Marketplace has launched, further enabling blockchain developers! Learn more

How to build an Ethereum URL shortener dApp

September 23, 2022

Overview

dApps (decentralized applications) are an integral part of the Ethereum development ecosystem. There are thousands of dApps already residing on the Ethereum blockchain. In this guide, we will learn how to build an Ethereum dApp that will store short versions of URLs.

What is a dApp?

dApps or Decentralized applications are pretty similar to traditional applications, they too have a frontend and backend. The difference is that dApps’ backends run on a decentralized peer-to-peer network, whereas traditional applications have their backend running on centralized servers. 

Ethereum dApps consist of frontends and smart-contracts. We can build the frontend/user interface in any language that is capable of communicating with the backend. The frontend can also be decentralized and hosted on storage services such as IPFS or Swarm.

The dApp backends, which are smart-contracts, are primarily written in Solidity - a high-level object-oriented language, and then deployed to the Ethereum blockchain network. The frontend can then interact with the smart-contract by executing parts of the underlying smart contract code.

To learn more about writing and deploying smart contracts, check our Solidity Guide

Why build a URL shortener on Ethereum?

Since the dawn of the Internet, the amount of data that it handled had been increasing continuously at exponential levels. All this information -  personal data, financial data, media, etc., resides on and is handled by central servers. The companies that run these central servers have full control over this data and can either misuse it, sell it or altogether deny access to this data that they don’t technically own. The centralized server approach is not resilient against censorship or other forms of negligence. A great example of this is Google shutting down their URL shortener. This is where dApps have a significant advantage over traditional apps; dApps help decentralize information and make the internet more secure and accessible for everyone. At no point in time, one single company can possess your data. The data is simply spread out over many locations (decentralized). 

Now that it is clear why dApps are so useful, it’s time to build an alternative URL shortener on Ethereum:

Prerequisites
  • NodeJS installed on your system
  • Git installed on your system
  • A text editor
  • Terminal aka Command Line
  • Metamask and some ETH for gas fees

Booting our Ethereum node

For the purpose of this guide, we could use any Ethereum client, such as Geth or OpenEthereum (fka Parity). Since launching those clients is quite an involved and time consuming process, we will grab a free endpoint from QuickNode to make it completely painless. 

Signup for QuickNode, and launch your free node on ETH mainnet.

After you've created your free ethereum node, copy and save your HTTP Provider endpoint as you will need it later:

Screenshot of QuickNode Ethereum Endpoint


Making our dApp

Make sure NodeJS and git are installed on your machine. 

To check if NodeJS 10+ is installed on your system, type the following in your terminal/cmd:

making our dapp

Copy
$ node -v

If not installed, you can download the LTS version of NodeJS from the official website.

To check if git is installed on your system, type the following in your terminal/cmd:

making our dapp

Copy
$ git --version

If not installed, you can download git from the official website

We'll make/build a URL shortener called 0xsu, which repositories located here on GitHub


The solidity contract + explanation

Before building our dApp; first, let’s understand the smart contract on which we’ll build our dApp.

pragma solidity ^0.5.2;

contract URLShortner {
  struct URLStruct {
    address owner;
    string url;
    bool exists;
    bool paid;
  }
  mapping (bytes => URLStruct) lookupTable;
  mapping (address => bytes[]) public shortenedURLs;
  address[] accts;
  address payable owner;
  event URLShortened(string url, bytes slug, address owner);

  constructor() public { owner = msg.sender; }

  function shortenURLWithSlug(string memory _url, bytes memory _short, bool paid) public payable {
    bool paidDefault = false;
    if (!lookupTable[_short].exists){
      lookupTable[_short] = URLStruct(msg.sender, _url, true, paid||paidDefault);
      shortenedURLs[msg.sender].push(_short);
      if(shortenedURLs[msg.sender].length < 1) {
        accts.push(msg.sender);
      } 
      emit URLShortened(_url, _short, msg.sender);
    }  
  }

  function shortenURL(string memory url, bool paid) public payable {
    bool paidDefault = false;
    bytes memory shortHash = getShortSlug(url);
    return shortenURLWithSlug(url, shortHash, paid||paidDefault);
  }

  function listAccts() public view returns (address[] memory){
    return accts;
  }

  function getURL(bytes memory _short) public view returns (string memory) {
    URLStruct storage result = lookupTable[_short];
    if(result.exists){
      return result.url;
    } 
    return "FAIL";
  }

  function kill() public {
    if (msg.sender == owner) selfdestruct(owner);
  }

  // privates
  function getShortSlug(string memory str) internal pure returns (bytes memory) {
    bytes32 hash = sha256(abi.encodePacked(str));
    uint main_shift = 15;
    bytes32 mask = 0xffffff0000000000000000000000000000000000000000000000000000000000;
    return abi.encodePacked(bytes3(hash<<(main_shift*6)&mask));
  }
}

Explanation of the code above: 

Line 1: Declaring the solidity version

Line 3-9: Declaring our smart contract as URLShortner, then creating a struct URLStruct to store/group to store the relevant details of a shortened URL; owner of type address which stores the address of the person shortening the URL, url of type string to store the URL to be shortened, exists of type boolean type will store if the URL is present, paid of type boolean to check if the transaction to shorten the URL is paid.

Line 10: Create a mapping to map the short version of the URL to URLStruct.

Line 11: Create a mapping where a particular owner’s address (a person shortening the URL) will be mapped to an array of shortened URLs that person has shortened. So, every individual shortening URLs will have an array of shortened URLs mapped to their address.

Line 12: Creating an array named accts of type address which will store the addresses of accounts who have interacted with the smart contract by shortening the URL.

Line 13: Declaring owner of type address payable which will allow that owner to access primitives useful to manage ethers.

Line 14: Creating an event URLShortened which will be emitted via the RPC on ethereum nodes worldwide - any front end can listen for this event.

Line 16: Creating a constructor and specifying the address of the creator of the smart contract - this address has special privileges.

Line 18-28: 
  • Creating a function shortenURLWithSlug which will allow a person/individual to specify the shortened version of the URL, the function is of state public which means it can be accessed outside scopes of this function and by other contracts. 
  • Setting paidDefault as false.
  • An if condition to check if the short version of the given URL exists or not and if it does not exist it will be added to the lookupTable using a URLStruct.
  • Adding the short URL (slug) to the person shortening the URL in the shortenedURLs.
  • A condition to check if the number of URLs shortened by the person/individual is less than one and if the person/individual is shortening the URL for the first time then it will be added to the accts array which we saw on line 12.
  • Emitting the event URLShortened.

Line 30-34: Creating a function shortenURL, this function is the same as the previous one except the person/individual doesn't get to specify the short version, they paste the URL and it will be shortened using the getShortSlug method which we’ll see later.

Line 36-38: Creating a function listAccts which will facilitate a getter method to get the accounts from the accts array as the accts isn’t public.

Line 40-46: Creating a function getURL, to check if the short URL exists in the lookupTable and if it does it will return the shortened URL and if it doesn’t it will return the word FAIL.

Line 48-50: Creating a function kill and an if condition in it which will check if the owner (the person from line 14) is connected to the contract, if yes then it’ll stop the connection/association.

Line 53-58: Creating a function getShortSlug which is a private method by which we generate the short URL.

To save time and ETH, we've already deployed this smart contract and it's address is embedded in our helper libraries and the code below.

The front end

Start by creating a directory for our dApp and then clone the required front-end repositories into it:

the front end

Copy
$ mkdir 0xsu

the front end

Copy
$ cd 0xsu

the front end

Copy
$ git clone https://github.com/dU4E/0xsu-front-end/

the front end

Copy
$ cd 0xsu-front-end

Our app’s front-end is made using react so, let’s install all the necessary dependencies.

the front end

Copy
$ npm i react

the front end

Copy
$ npm install -g create-react-app

The frontend will run on port number 3000, and port 3001 is where the express server will run in the next step.

Now let’s understand some important parts of the main file of our front end react app:

the front end

Copy
import React from "react";
import Du4e from "@sahilsen/0xsu-js-lib";
import "./App.css";
import Navbar from "react-bootstrap/Navbar";
import { Badge, Container, Form, Modal, Button, Col } from "react-bootstrap";

class App extends React.Component {
  state = {
    shortURL: "",
    copied: null,
    urls: [],
    show: false
  };

  constructor(props) {
    super(props);
    this.shortenUrl = this.shortenUrl.bind(this);
    this.copy = this.copy.bind(this);
    this.handleClose = this.handleClose.bind(this);
    this.viewAcct = this.viewAcct.bind(this);
    this.shortener = new Du4e();
    this.shortener.onTxSend = txid => {
      console.log("TXID", txid);
      this.setState({ shortURL: "Waiting for tx to confirm" });
    };
    this.shortener.onURLShortened = (err, result) => {
      console.log("result", result);
      this.setState({ shortURL: result.args.slug });
    };
  }

  copy() {
    let el = document.createElement("textarea");
    el.value = this.state.longURL;
    el.setAttribute("readonly", "");
    el.style.position = "absolute";
    el.style.left = "-9999px";
    document.body.appendChild(el);
    el.select();
    document.execCommand("copy");
    document.body.removeChild(el);
    this.setState({ copied: true });
  }

  handleClose() {
    this.setState({ show: false });
  }

  viewAcct() {
    this.shortener.listOfUrls(list => {
      console.log(list);
      this.setState({ urls: list });
    });
  }

  shortenUrl(evt) {
    evt.preventDefault();
    this.shortener.shortenUrl(this.refs.short.value, { cb: () => {} });
  }

  render() {
    let { shortURL } = this.state;
    return (
      <div>
        <Navbar bg="light">
          <Navbar.Collapse className="justify-content-end">
            <a href="#" onClick={this.viewAcct}>
              Account
            </a>
          </Navbar.Collapse>
        </Navbar>
        <Container className="text-center">
          <br />
          <h1 className="App">0xSU.co</h1>
          <p>
            Your friendly neighborhood decentralized Ethereum Short URL maker
          </p>
          <br />
          <form onSubmit={this.shortenUrl}>
            <Form.Row>
              <Col sm={9}>
                <Form.Control
                  size="lg"
                  type="text"
                  ref="short"
                  placeholder="Paste your long URL here"
                />
              </Col>
              <Col sm={3}>
                <Button variant="primary" size="lg" type="submit">
                  Shorten
                </Button>
              </Col>
            </Form.Row>
          </form>

          <div className={shortURL ? "" : "hidden"}>
            <br />
            <br />
            <span className={shortURL.startsWith("Waiting") ? "loading" : ""}>
              {shortURL.startsWith("Waiting")
                ? shortURL
                : `http://localhost:3001/${shortURL}`}
            </span>
            &nbsp;
            {!shortURL.startsWith("Waiting") && (
              <Badge variant="success" onClick={this.copy} className="copy">
                {this.state.copied ? "Copied!" : "Copy"}
              </Badge>
            )}
          </div>
          <br />
          <br />
          <br />
          <br />

          <a href="https://github.com/du4e">Learn more / source code</a>
        </Container>
        <Modal
          show={this.state.urls.length > 0 || this.state.show}
          onHide={this.handleClose}
        >
          <Modal.Header closeButton>
            <Modal.Title>Your Shortened URLs</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            {this.state.urls.map(x => {
              return (
                <p key={x}>
                  <a href={`http://localhost:3001/${x}`}>{x}</a>
                </p>
              );
            })}
          </Modal.Body>
        </Modal>
      </div>
    );
  }
}

export default App;

Line 1-5: Importing the necessary libraries and assets.

Line 7: Establishing a component called App

Line 8-13: Initializing the state which will help in fetching remote data (from the contract).

Line 15-19: Setting bindings of methods to be used - so they use the right "this".

Line 20: Setting up object Du4e of Du4e library which is a library for wrapping URLs.

Line 21-28: Attaching two event handlers, onTxSend which will listen for the transaction, and onURLShortened which will listen for event URLShortened from the contract and when the URLShortened event is emitted it will change the state and make the shortURL available to the react component App.

Line 30-41: copy() establishes a temporary element on the page that allows the text of shortened URL to be copied.

Line 43-45: handleClose() closes the Modal, which a message box with functionalities.

Line 47-52: viewAcct() opens a Modal that will display the list of shortened URLs by the current user by setting the state of URLs for that user.

Line 54-57: shortenUrl shortens the URL using the Du4e library.

Line 59-143: A bunch of JSX that attaches events and renders the app.

The back end

Now, we need to set up our server and the smart-contract (Which we saw earlier). We need to go back to our parent directory /0xsu and clone the smart-contract repository and the express-server repository into it.

Note: we are using express-server, but you can choose from other options such as Sinatra and Flask from here

the back end

Copy
$  git clone https://github.com/dU4E/0xsu-smart-contracts

Note: The smart-contract for this app is already deployed on the Ethereum blockchain, and we can query the contract functions using its ABI and the contract address.

Now, go back to the parent directory 0xsu from a new terminal/cmd window and clone the express-server repo.

the back end

Copy
$ git clone https://github.com/dU4E/0xsu-express-server

the back end

Copy
$ cd 0xsu-express-server

We'll also need to install express; type the following in your terminal/cmd:

the back end

Copy
$ npm i express

Open the .env file in your text editor, and on the second line, replace ADD_YOUR_QUICKNODE_URL with your QuickNode HTTPS URL that you previously saved.

Let’s understand what actually our express server does by seeing the index.js file from the 0xsu-express-server repository.

the back end

Copy
const fs = require('fs')
const bodyParser = require('body-parser')
const express = require('express')
const Web3 = require('web3')
const path = require('path')
const dotenv = require('dotenv').config({ path: path.resolve('.env')})
const app = express()
const port = 3001
const abi = JSON.parse(fs.readFileSync(process.env.ABI_PATH))
const contractAddr = process.env.CONTRACT_ADDRESS
const web3 = new Web3(Web3.givenProvider || process.env.PROVIDER_URL)

app.set('view engine', 'ejs')

app.get('/:short', async function (req, res) {
  const contract = new web3.eth.Contract(abi, contractAddr)
  let slug = req.params.short
  // grab the url and if it's been paid or not
  let destination = await contract.methods.getURL(slug).call()
  res.redirect(destination !== '"FAIL' ? destination : "/")
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Explanation of the code above:

Line 1-6: Importing the necessary packages/libraries.

Line 7-11: Creating a new express application, declaring the port number on which our express app will run, declaring a variable abi which will store the ABI of the smart contract fetched from .env file, declaring a variable contractAddr which will store the smart contract’s address fetched from .env file, a variable web3 which will connect to the QuickNode endpoint which we pasted in the .env file.

Line 13: Setting the EJS as the view engine for our express app.

Line 15-21: Getting the short version of the URL, setting the web3 using abi and contractAddr, fetching the short URL and storing it in the variable slug, getting information on URL from smart contract and if the short URL provided exists it will redirect to the actual URL.

Line 23: Printing a message in the console, which will be printed upon successful execution of the express app’s index.js code.

You should now have all the necessary parts of the code installed and two terminal windows open, one with 0xsu-front-end directory and the other with 0xsu-express-server.

Putting everything together

  1. Running the Server

Switch to the window with /express-server directory opened and start the express server by typing the following command:

putting everything together

Copy
$ node index

The output will look like this:


Now, switch to the other terminal window and start the react app by typing the following in your terminal/cmd:

putting everything together

Copy
$ npm start 

If everything goes right, you should be able to open the app in your browser at http://localhost:3000/, provided you have the Metamask browser plugin, a window will open to ask you to grant permission to the app.


  1. Testing your dApp

Place any URL that you wish to shorten in the text field and click Shorten. It will open the Metamask to confirm the transaction that will require a gas fee. As we are interacting with the smart-contract deployed on the Ethereum blockchain network, we need to pay a fee for it. You can learn more about transactions and gas fees in this guide - [How to resend a transaction with higher gas price using ethers-js

After the transaction is completed, you will see a message with the shortened URL below the text field: 


You can also go to the Account section from the top-right and click on the link to open the actual link at https://localhost:3001/shortenedURL.


The generated shortened URL, will stay on the Ethereum network and can be accessed anytime.

Conclusion

Congratulations on building a real decentralized App! With a little help from QuikNode we hope your process was quick and painless.

Subscribe to our newsletter for more articles and guides on Ethereum. If you have any feedback, feel free to reach out via Twitter. You can always chat with us on our Discord community server, featuring some of the coolest developers you’ll ever meet :)

Related articles 14

How to connect to Ethereum using .NET (Nethereum)
Originally Published On: Feb 20, 2021
Updated On: Sep 23, 2022

Dotnet or .NET is very popular... .NET is Microsoft’s alternative... C# is a modern Object-Oriented... Nethereum is the .Net... Nethereum requires .NET Core or... We could use pretty much any... Now go to your .NET app folder... Here we saw how we can connect...

Continue reading
What are Ethereum Transactions?
Originally Published On: Mar 28, 2022
Updated On: Sep 23, 2022

Transactions in Ethereum are... A transaction usually consists... Now let's take everything that... Start by opening up your... That's a wrap! In this guide, we...

Continue reading
How to integrate IPFS with Ethereum
Originally Published On: Apr 8, 2021
Updated On: Sep 23, 2022

It can be costly to store... The web that we use today is... Whenever someone wants to... Blockchains like Ethereum... Our first step here would be to... Now that we’ve published our... Head over to the To query the Ethereum... We’ll use Step 1: ipfs.js... So now that you know how to...

Continue reading
How to Fork Ethereum Blockchain with Ganache.
Originally Published On: Apr 8, 2021
Updated On: Sep 23, 2022

Forking and running a local... Ganache is an Ethereum developer... A fork in software development... We could use pretty much any... We’ll use To fork the mainnet, open your... Now, let’s get some information... Now that you have a local...

Continue reading
How To Fork Ethereum Mainnet with Hardhat
Originally Published On: Jul 13, 2021
Updated On: Sep 23, 2022

Forking the chain at an older... We could use any Ethereum client... Hardhat can be installed through... The forked chain's RPC server is... QuickNode offers access to Full... In this tutorial, we learned...

Continue reading
How to create and deploy a smart contract with Hardhat
Originally Published On: Jun 11, 2021
Updated On: Sep 23, 2022

Ethereum development... We’ll install hardhat using We’ll need an Ethereum... We’ll deploy our contract on the... To deploy our contract on... Open the hardhat.config.js file... Now, for our contract, create a... Now to deploy our contract,... Here we saw how to work with...

Continue reading
How to Fetch Ethereum Event Logs in Ruby
Originally Published On: Mar 6, 2021
Updated On: Sep 23, 2022

Ethereum log records are very... In event-driven languages like... eth.rb is a Ruby gem that makes... We’ll first deploy a smart... We could use pretty much any... Installing the eth.rb... Congrats! You've learned about...

Continue reading
How to Send an EIP-1559 Transaction
Originally Published On: Dec 3, 2021
Updated On: Sep 23, 2022

While Ethereum has been trying... The first step on our journey to... We will need some Kovan test ETH... We will use the Create a javascript file named... I hope now you have a better...

Continue reading