DEV Community

Cover image for Introducing Pckd - The most powerful ๐Ÿ’ช URL shortener
Mohit Yadav
Mohit Yadav

Posted on • Updated on

Introducing Pckd - The most powerful ๐Ÿ’ช URL shortener

Overview of My Submission

This month, I saw the Azure + Dev.to Hackathon ๐Ÿคฉ and immediately knew I found the excuse to spend my time bringing my prolonged idea to life.

What exactly is ๐Ÿ”— Pckd?

Intro Video

Have you ever wanted to share a long link with a friend, but it seems way too long to send through a message (maybe it's a base-64 image URI), so you might turn towards URL shorteners, nothing special?

But try to imagine, along with the shortening functionality, what if you could have a lot more. I mean, track all the clicks (or hits) on the URL that you shortened and get very-deep insights about the person who clicked it, like:

  • Their ๐Ÿ’ป device information, including model, operating system, etc.
  • Their ๐Ÿ‘ฉโ€๐Ÿ’ป browser name and version
  • Their ๐ŸŒŽ IP location, along with their ISP name, their city, rough coordinates, etc.

And much more! ๐Ÿคฏ

And to add the cherry on the top, you could host it yourself on your custom domain! In fact, it was created to be self-hosted, for people on the self-hosted community

Doesn't that sound like the time to switch from Bitly and stop paying the premium, or being limited by 100 links/month?

Submission Category

Computing Captains: Since this app is hosted on an azure virtual machine.

Creation process ๐Ÿ‘ฉโ€๐ŸŽจ

The creation process was undoubtedly the most fun part of the project. I solved many new challenges, learnt a lot, and finally came up with an application I was proud of! Here is how I pulled it off...

Designing ๐ŸŽจ

Usually when I create a pet project, I just use the designing from the previous projects I was working on, since they won't get noticed anyway. But with this one, I wanted it to reach to the top of the list of open source URL shorteners. I had self-doubts about whether I would be able to reach the goal with my experience, but you are free to judge.

Inspiration โœจ

So, I started by looking for inspiration. The best place for this was Dribbble. I went to the inspiration tab. Many people would create the best design they could without any inspiration, but as Pablo Picasso (may have) said:

Good artists create, great artists steal

That's one way to put it, but the point is: with the amount of design experience and the height of the goals I had, I just wasn't going to be able to create a good-enough design overnight if I started from scratch. Hence, the searching.

Dribbble.gif

After endless scrolling, similar-sounding keywords and hard-to-make choices, I decided to go with this design, because it just looked great for the purpose I was looking for. There were many fancy ones too, but they just weren't practical, or too flashy for the typical programmer that was going to host it (if Pckd was even going to be successful).

Customizing ๐Ÿ•

The original design from Dribbble was good, but it wanted something more, like the colour customization, planning out exactly where everything would go, and creating an exact mock-up of how the app would look and much more. Therefore, everything was already planned out before I touched any code.

I find this approach very useful, because you don't have to scratch your head over minor details that would otherwise go unnoticed while planning, and you have to go to the sketch board all over again if you've started coding.

Dashboard ๐Ÿ’พ

I again went on to Dribbble to find some dashboard designs. I got some that caught my attention. After that, I went to Figma and started laying out the design

After endless scratching of heads, I got this:

dashboard.png

(One of my proudest creations ๐Ÿ˜Ž)

Other pages ๐Ÿ—ž

As for the other pages, I created the homepage out of just my imagination. Here's how it looked:

home.png

And the signup pages:

signup.png

The designs looked good in my opinion, and I was dead-set to implement the most design-accurate version of this with react and try to not mess up the margins or paddings somewhere. This marked the end of the designing and planning part of the app.
Not bad, right?

Setting up the backend ๐Ÿงฉ

For the backend, I started out by forking a backend template repo (made by me ;), because it contains a file-based structure, just like NextJS, but for the backend. It also has authentication baked in, so one doesn't have to go through the hassle of setting everything up.

The database schema ๐Ÿ’พ

OK, I want to have a hits table, a user table, and..., and that's it. Oh! Forgot the pckd table, the main one! After all these thoughts back and forth in my mind, I was ready with the following database schema

structure.png

I created the database through the Prisma schema, and it ended up working great.

The file structure ๐Ÿ“

This is how the finished file structure looked like:

Screenshot 2022-03-01 at 8.00.42 AM.png

Adding a new route was just as easy as creating a new file in the directory, like for the user type, api/User/index.graphql file needed to be created. This is how it looked like:



type User {
  id: ID!
  pckds: [pckd!]
  name: String!
  email: String!
  createdAt: String!
  updatedAt: String!
}


Enter fullscreen mode Exit fullscreen mode

And For the resolving the users' pckd logic, in the api/User/index.js file:



module.exports = {
  User: {
    pckds: async (parent, args, { prisma }) => {
      const pckds = await prisma.pckd.findMany({
        where: {
          userId: parent.id,
        },
      });
      return pckds;
    },
  },
};


Enter fullscreen mode Exit fullscreen mode

As simple as creating an export file. You should definitely try this method of creating backends out. Little boilerplate, powerful APIs. ๐Ÿ˜ƒ๐Ÿ’ช

Achieving this functionality โ›ฝ

For combining the files and graphql schemas, and create a single instance of typeDef and resolvers, I used the following code



const path = require("path");
const { loadFilesSync } = require("@graphql-tools/load-files");
const { mergeTypeDefs, mergeResolvers } = require("@graphql-tools/merge");
const { makeExecutableSchema } = require("@graphql-tools/schema");

const typeDefs = loadFilesSync(path.join(__dirname, "/api/**/*.graphql"));
const resolvers = loadFilesSync(path.join(__dirname, "/api/**/*.js"));

const schemaWithResolvers = makeExecutableSchema({
  typeDefs: mergeTypeDefs(typeDefs),
  resolvers: mergeResolvers(resolvers),
});

module.exports = schemaWithResolvers;


Enter fullscreen mode Exit fullscreen mode

This would combine all the files ending with the .js and .graphql extension from the api and it's sub-folder into a single typedef and resolvers variable, and export them as schema.

Querying the backend ๐Ÿ’ฌ

For those who are new to GraphQL or haven't used apollo GraphQL before, Apollo provides a great interface for testing and creating queries, just like postman, but for Graphs, and much more powerful.

And creating APIs with GraphQL never gets easier

never gets easier.webp

Setting up the frontend โš›

For the frontend, I used React with the Create-react-app template, because it was easiest to work with, and I didn't use NextJS because the app didn't have much about SEO.

Along with react, I used Redux, and Apollo-graphql-client to query the backend, because the global state was shared across multiple components. Also, I wanted to learn Redux, and what other way to learn than to implement it yourself. (But it turned out to be an overkill ๐Ÿ˜…).

The typical React development pattern continues... At the end, I had a polished URL-shortener application that was fully customizable (upto the name) and ready to be deployed. Let's go!

Hosting it

For hosting the web app, I used azure VMs, as per the instructions. I created a B2s virtual machine, installed nginx, MySQL, NodeJS, NPM and cert bot, then ran the NodeJS server.

Azure Portal Screenshot

The React frontend was built to a static file, which the backend served itself. I only had to set up a nginx proxy pass to redirect internet traffic from external port 80/443 (the web traffic port) to internal port 4000 (NodeJS server port).

Here is how my default configuration file looked like:



server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        location / {
                proxy_pass http://127.0.0.1:4000/;
                proxy_http_version 1.1;
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
                proxy_pass_header  Set-Cookie;

                proxy_set_header   Host               $host;
                proxy_set_header   X-Real-IP          $remote_addr;
                proxy_set_header   X-Forwarded-Proto  $scheme;
                proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;
        }
}


Enter fullscreen mode Exit fullscreen mode

(The SSL part is not included). Then I exposed the ports 80 and 443 from the azure portal, and the app was open for the world to access.

Try it yourself

Here are some useful links you would need:

I hope you liked the blog. If you did, please leave your thoughts in the comments. It would make my day.

Till then, happy ๐Ÿ’ป coding! ๐Ÿฟ

Top comments (28)

Collapse
 
just_moh_it profile image
Mohit Yadav

Thanks for comment.

As for the IPA situation, I'm actually fully aware of this. However, with the model the app is created with, that is scalability and convenience at its core, and using free-to-use (or generous free-tier) services, it may be more efficient to store the IP than to extract info (like geolocation), and then delete the IP.

That's because it uses the ip-registry API, which charges per API call to fetch isp and other info from an IPA, and only supports a few thousand lookups for free. So I wanted the person who hosted the app, to not be forced to track every single IP, and be able to only look up specific ones up on-demand to preserve API credits, in case a link goes viral. On-demand lookups were not possible without storing the IP address. Though this feature is currently on the roadmap, it is actively being worked on.

Even though I could have hidden it from the frontend, it's just a matter of changing a few lines in the backend code to be able to fetch the IP directly from the database.

As for, why I have it enabled on the version I'm hosting, I'll discard the hosting after the hackathon gets over in a matter of a few days (maybe), and only preserve the code, for people to host themselves.

Also, since this app was supposed to be self hosted and would not be available as a hosted service (at least not completely free), it's up to the app host and the link-creators to choose whether they want to track users or not. If they want to include their privacy policy, they have all rights.

I hope you understand. Thanks!

Collapse
 
renanfranca profile image
Renan Franca

Omg! ๐Ÿคฏ๐ŸŽ‰
Awesome! Will this demo be hosted "forever"?
You are a great designer and developer, a complete professional ๐Ÿ‘

Collapse
 
just_moh_it profile image
Mohit Yadav

Haha, don't embarrass me! I haven't even graduated from high school yet ๐Ÿ˜„

Jokes aside, as for the hosting, this particular demo won't be hosted forever, but the one with the link on the GitHub repo will be, and it's much faster!

And really thanks for the heart-melting comment, means a lot! ๐Ÿ˜ƒ

Collapse
 
itsrakesh profile image
Rakesh Potnuru

Nice project.
What you used to create that product animation? Or you animated by yourself?

Collapse
 
just_moh_it profile image
Mohit Yadav

Thanks! I plan on creating the animations using Framer motion in the next revision of the app. The on in the GIF was simply created with After Effects. Hope this helps ๐Ÿงก

Collapse
 
itsrakesh profile image
Rakesh Potnuru • Edited

Can you share me After Effects template?

Thread Thread
 
just_moh_it profile image
Mohit Yadav

Haven't used the template as such

I used jitter.video to create the UI animation (the graph animations and fade in entry to elements) by exporting my figma designs using a their Figma plugin. The design files:

I exported these to 1080p (premium feature but you can get it for free by hacking their website (burp-suite + dev tools to change the is_subscription_active repsonse)) but I would not suggest doing that. I only did that because I was stuck with my hardwork going to vain because I didn't notice I could not export it to more than 720p without a subscription

Then, after exporting these to .mp4 files, I followed along this video to make the perspective-based animations, and it turned out pretty great!

Hope it helps โค๏ธ, cause I now wish I knew it when I just started out!

Thread Thread
 
itsrakesh profile image
Rakesh Potnuru

Thank you so much

 
just_moh_it profile image
Mohit Yadav

No problem, I was asked the same question by many, so did not mind adding it here.

Collapse
 
namankatewa profile image
Naman Katewa

Just another small request buddy. Idk much about url shortening and all. But could the redirecting be made any faster. If not then it's cool.

Collapse
 
just_moh_it profile image
Mohit Yadav

Redirects are faster than ever, also added animations and graphs. Use this version if speed is a concern: yayy.me

Collapse
 
namankatewa profile image
Naman Katewa

Yeah thanks

Collapse
 
just_moh_it profile image
Mohit Yadav

Yep, sure! Adding direct redirects using express js (used for API currently) is on the roadmap. It would hopefully be done by the next time you check it out.

Collapse
 
jacksonkasi profile image
Jackson Kasi

really cool ๐Ÿ˜Ž

Collapse
 
just_moh_it profile image
Mohit Yadav

Thanks you so much! Made my day ๐Ÿ˜Š

Collapse
 
timhuang profile image
Timothy Huang

Great! You do a great job. Thanks for the sharing.

Collapse
 
zippytyro profile image
Shashwat Verma

Cool design, the project demo link is broken?

Collapse
 
just_moh_it profile image
Mohit Yadav

The website is up, forgot to use pm2 and closed the shell, so the server stopped. Thanks for pointing it out ๐Ÿ˜„

Collapse
 
thomasbnt profile image
Thomas Bnt โ˜•

Nice, but not responsive ๐Ÿ˜

Collapse
 
just_moh_it profile image
Mohit Yadav

Yes, I wasn't able to create a good enough concept for the dashboard on mobile devices, because the layout is quite unusual. I plan on adding support through dropdown boxes though very soon.

For now, landscape mode (with a bit of zooming out works well).

Collapse
 
namankatewa profile image
Naman Katewa

It's good man Even I signed up for it and am going to use it extensively.

But one problem though it's not responsive.(Could be easily fixed. :) )

The post is so well written ๐Ÿ˜„๐Ÿ˜„

Collapse
 
just_moh_it profile image
Mohit Yadav

Thank you so much man! Really appreciate that. I will surely make it responsive and fix some issues within a week or so. Stay tuned ๐ŸŽ‰