DEV Community

VNadygin
VNadygin

Posted on

Make your links human-friendly

The reality of long URLs is supposed to be light years behind us. However, I still encounter them on almost every website. It is hard to figure out the best URL-shortener code that would also track how many times the link was opened.

Using Redwood JS Framework which combines some of the most popular and advanced frameworks, I am going to create such a service.

Initialise the project by following the official Get Started tutorial:

Initialize git

git init
Enter fullscreen mode Exit fullscreen mode

The first step is to add a new data table to api/db/schema.prisma

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider      = "prisma-client-js"
  binaryTargets = "native"
}

model WebLink {
  id           Int    @id @default(autoincrement())
  shortId      String @unique
  url          String
  viewedAmount Int    @default(0)
}
Enter fullscreen mode Exit fullscreen mode

Next, the Prisma framework can help us create a SQL migration for the sqlite. I recommend using postgresql in production.

yarn rw prisma migrate dev

Redwood did a great job with the scaffold feature that generates a CRUD (Create, Retrieve, Update, Delete) code for React by running just one command.

yarn rw g scaffold web-link

Run the development mode
yarn redwood dev

You will see that we have not finished the home page yet; however, four pages have already been generated:
/web-links/new
/web-links/{id:Int}/edit
/web-links/{id:Int}
/web-links

Image description

Let's open http://localhost:8910/web-links and create a new data record.

Now we want to add a new query to the Graphql API:
api/src/graphql/webLinks.sdl.js

Add webLinkByShortId(shortId: String!): WebLink @requireAuth to the SDL schema

export const schema = gql`
  type WebLink {
    id: Int!
    shortId: String!
    url: String!
    viewedAmount: Int!
  }

  type Query {
    webLinks: [WebLink!]! @requireAuth
    webLink(id: Int!): WebLink @requireAuth
+   webLinkByShortId(shortId: String!): WebLink @requireAuth
  }

  input CreateWebLinkInput {
    shortId: String!
    url: String!
    viewedAmount: Int!
  }

  input UpdateWebLinkInput {
    shortId: String
    url: String
    viewedAmount: Int
  }

  type Mutation {
    createWebLink(input: CreateWebLinkInput!): WebLink! @requireAuth
    updateWebLink(id: Int!, input: UpdateWebLinkInput!): WebLink! @requireAuth
    deleteWebLink(id: Int!): WebLink! @requireAuth
  }
Enter fullscreen mode Exit fullscreen mode

The next step is creating a function that will resolve this for us:
open api/src/services/webLinks/webLinks.js

import { db } from 'src/lib/db'

export const webLinks = () => {
  return db.webLink.findMany()
}

export const webLink = ({ id }) => {
  return db.webLink.findUnique({
    where: { id },
  })
}

export const createWebLink = ({ input }) => {
  return db.webLink.create({
    data: input,
  })
}

export const updateWebLink = ({ id, input }) => {
  return db.webLink.update({
    data: input,
    where: { id },
  })
}

export const deleteWebLink = ({ id }) => {
  return db.webLink.delete({
    where: { id },
  })
}

+ export const webLinkByShortId = async ({ shortId }) => {
+   await db.webLink.update({
+     where: { shortId },
+     data: { viewedAmount: { increment: 1 } },
+   })
+   return db.webLink.findUnique({ where: { shortId } })
+ }
Enter fullscreen mode Exit fullscreen mode

I used the Redwood generator to generate a page.
yarn redwood g page Redirect

Update web/src/Routes.js. Make sure that the new route is the last on the list since it uses dynamic params.

  - <Route path="/redirect" page={RedirectPage} name="redirect" />
  + <Route path="/{shortId}" page={RedirectPage} name="redirect" />
Enter fullscreen mode Exit fullscreen mode

Now we want to generate a cell. This is the way Redwood recommends working with data:
yarn redwood g cell redirect

Modify web/src/components/RedirectCell/RedirectCell.js

- export const QUERY = gql`
-  query FindRedirectQuery($id: Int!) {
-    redirect: redirect(id: $id) {
-      id
-    }
-  }
- `

+ export const QUERY = gql`
+   query FindWebLink($shortId: String!) {
+     link: webLinkByShortId(shortId: $shortId) {
+       id
+       url
+     }
+   }
+ `

- export const Success = ({ redirect }) => {
-   return <div>{JSON.stringify(redirect)}</div>
- }


+ export const Success = ({ link }) => {
+   React.useEffect(() => {
+     if (!link) return
+
+     window.location = link.url
+   }, [link])
+
+   return 'redirecting...'
+ }


Enter fullscreen mode Exit fullscreen mode

As a result, if you click on [/kcuobL](http://localhost:8910/kcuobL), the link will take you to https://google.com.

Image description

Voila. There is more than just a URL shortener in your hands. It is a marketing tool that can help your company's specialists to organise a huge part of their work online.

Top comments (1)

Collapse
 
moopet profile image
Ben Sinclair

Make your links human-friendly

I think this approach does the opposite - having a short hash-style link instead of something readable would be less human-friendly, because then you have no chance to know where the link is going before you click on it. Rick-rolling, malware, tracking, etc. are all hidden behind shortened URLs.

I'd also not consider hiding your tracking behind shortened URLs to be the friendliest approach. People value privacy more and more as the world tries to take more of it away.

Finally, what you (and everyone else who makes shorteners) are doing is adding another point of failure. With tracking appended in the query string (like those ubiquitous ?utm= fragments) or by using the referer value you don't need a third-party involved at all. If your URL shortener service has downtime, then anyone's links which use it also break. If your database gets corrupted, it's a mess for everyone.