Skip to main content

How to build an Ethereum URL shortener dApp

Updated on
Dec 11, 2023

16 min read

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

Set Up Your QuickNode Endpoint​

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 create a free QuickNode account and create an ETH mainnet endpoint to make it completely painless. 

After you've created your free Ethereum endpoint, 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:

$ 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:

$ 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:

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

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

$ npm i react
$ 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:

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

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

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

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

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

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:

$ 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:

$ 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 :)

Share this guide