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.
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
Adding TypeScript
Create a tsconfig.json
file in the root folder and run the below command.
yarn add --dev typescript @types/react @types/node
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;
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
Main Logic
The idea is to store a JSON Object of the below format
{
"url":"https://dev.to",
"short_url":"547382"
}
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);
}
};
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);
}
};
That's it for the backend!
Frontend
We basically need 2 routes
- To create short URLs
- 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;
Output
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;
That's it! To run locally use the below command
yarn dev
Give a โญ