DEV Community

Cover image for How I made my own bitly clone using NextJS and FaunaDB ๐Ÿ”ฅ
Manitej โšก
Manitej โšก

Posted on • Updated on

How I made my own bitly clone using NextJS and FaunaDB ๐Ÿ”ฅ

I use bit.ly a lot to shorten my URLs but their dashboard is cluttered. I mean there's a lot more on the website which I didn't like. So I tried to make something similar but with only focus on shortening links. So, here is what I did.

Alt Text

Tech I Used

  • Typescript
  • FaunaDB
  • NextJS

This is the first time I'm working with FaunaDB and TypeScript so I'm super excited!

Code

Creating the NextJS project

Run the below command to start an empty NextJS project

npx create-next-app url-shortener
Enter fullscreen mode Exit fullscreen mode

Adding TypeScript

Create a tsconfig.json file in the root folder and run the below command.

yarn add --dev typescript @types/react @types/node
Enter fullscreen mode Exit fullscreen mode

rename _app.js to _app.tsx and paste below code

import type { AppProps /*, AppContext */ } from "next/app";
import "../styles/globals.css";

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

Dependencies

  • axios (for API Calls)
  • faunadb (for serverless DB)
  • generate-unique-id (for generating short URLs)

Creating Database

  • Go to faunadb
  • Create a free account and login
  • Create a database
  • Create a collection named urls
  • Go to keys section and create a key and copy it
  • Create a .env.local file in root folder and paste the key as
NEXT_PUBLIC_FAUNA_KEY=YOUR_KEY
Enter fullscreen mode Exit fullscreen mode

Main Logic

The idea is to store a JSON Object of the below format

{
   "url":"https://dev.to",
   "short_url":"547382"
}
Enter fullscreen mode Exit fullscreen mode

Whenever a user enters {your-domain}/547382 they will be redirected to https://dev.to

Writing serverless functions

To make a short URL from original URL

Go to pages/api and create a file createUrl.ts

import type { NextApiRequest, NextApiResponse } from "next";
const generateUniqueId = require("generate-unique-id");
const faunadb = require("faunadb"),
  q = faunadb.query;

const client = new faunadb.Client({
  secret: process.env.NEXT_PUBLIC_FAUNA_KEY,
});

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const { url } = req.body;

  const id = generateUniqueId({
    length: 8,
    useLetters: false,
  });

  try {
    const info = await client.query(
      q.Create(q.Collection("urls"), {
        data: {
          ourl: url,
          surl: id,
        },
      })
    );

    res.status(200).send(id);
  } catch (error) {
    res.status(400).send(error.message);
  }
};
Enter fullscreen mode Exit fullscreen mode

To get original URL from short URL

Go to pages/api and create a file getShortUrl.ts

import type { NextApiRequest, NextApiResponse } from "next";
const faunadb = require("faunadb"),
  q = faunadb.query;

const client = new faunadb.Client({
  secret: process.env.NEXT_PUBLIC_FAUNA_KEY,
});

export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const ourl = await client.query(
      q.Map(
        q.Paginate(q.Match(q.Index("get_short_url"), req.body.url)),
        q.Lambda("X", q.Get(q.Var("X")))
      )
    );

    res.send(ourl.data[0].data.ourl);
  } catch (error) {
    res.status(400).send(error.message);
  }
};
Enter fullscreen mode Exit fullscreen mode

That's it for the backend!

Frontend

We basically need 2 routes

  1. To create short URLs
  2. To redirect users

1. To create short URLs

pages/index.tsx

import Axios from "axios";
import React, { useState } from "react";
import Head from "next/head";
const index = () => {
  const [url, setUrl] = useState<string>("");
  const [surl, setsUrl] = useState<string>("");
  const [load, setLoad] = useState<boolean>(false);
  const home =
    process.env.NODE_ENV === "development" ? "localhost:3000" : "zf.vercel.app";

  const getShortUrl = async () => {
    setLoad(true);
    await Axios.post("/api/createUrl", {
      url: url,
    })
      .then((res) => {
        setsUrl(`${home}/${res.data}`);
        setLoad(false);
      })
      .catch((e) => console.log(e));
  };
  return (
    <div className="container">
      <Head>
        <link rel="preconnect" href="https://fonts.gstatic.com" />
        <link
          href="https://fonts.googleapis.com/css2?family=Acme&display=swap"
          rel="stylesheet"
        />
        <title>URL Shortener ๐Ÿฑโ€๐Ÿš€</title>
      </Head>
      <h1 className="title">
        URL Shortener <span>๐Ÿ˜Ž</span>
      </h1>
      <input
        className="inp"
        placeholder="enter URL to be shorten"
        onChange={(e) => setUrl(e.target.value)}
      />
      <style jsx>{`
        .container {
          display: flex;
          padding: 10px;
          flex-direction: column;
          justify-content: center;
          align-items: center;
        }
        .title {
          font-family: "Acme", sans-serif;
          font-size: 20px;
        }
        .inp {
          padding: 20px;
          margin: 10px;
          width: 80%;
          border-radius: 5px;
          border: 1px solid #000;
          border-radius: 5px;
          text-align: center;
          font-family: "Acme", sans-serif;
          font-size: 20px;
        }
        .btn {
          padding: 10px 20px;
          margin: 10px;
          border: none;
          background: #3254a8;
          color: white;
          border-radius: 10px;
          font-family: "Acme", sans-serif;
          font-size: 20px;
          cursor: pointer;
        }
        .surl {
          font-family: "Acme", sans-serif;
          padding: 10px;
          margin: 10px;
          background-color: #32a852;
          border-radius: 10px 20px;
          color: white;
        }
      `}</style>
      <button onClick={getShortUrl} className="btn">
        {load ? "loading" : "Shorten"}
      </button>
      {surl.length > 0 ? <p className="surl">{surl}</p> : null}
    </div>
  );
};

export default index;
Enter fullscreen mode Exit fullscreen mode

Output

Alt Text

2. To create a redirect route

This part is tricky, we don't need to display anything to the user in this route. We simply need to redirect to the original URL from
the query in the URL

pages/[url].tsx

import Axios from "axios";
import { GetServerSideProps } from "next";

const Url = () => {
  return null;
};

export const getServerSideProps: GetServerSideProps = async (context: any) => {
  const { url } = context.params;

  const home =
    process.env.NODE_ENV === "development"
      ? "http://localhost:3000"
      : "https://zf.vercel.app";

  const info = await Axios.post(`${home}/api/getShortUrl`, {
    url: url,
  });
  return {
    redirect: {
      destination: info.data,
      permanent: true,
    },
  };
};

export default Url;
Enter fullscreen mode Exit fullscreen mode

That's it! To run locally use the below command

yarn dev
Enter fullscreen mode Exit fullscreen mode

Give a โญ

Top comments (0)