Creating a URL Shortener in Node and Now

Posted on December 01, 2019

sneaky-url, a URL Shortener service. This is a clone of services like bitly, or Google's URL Shortener.

It's built on now and uses hasura GraphQL. Let's break it down on how I did it.

This tutorial will not review setting up a Hasura instance, or your GraphQL-layered API. This tutorial assumes that you've already done that part and that your familiar with GraphQL.

init

First thing we need to do is initialize our project. Luckily, our project is pretty bare bones, so we don't need to use a generator! Create a new directory called url-shortener

mkdir url-shortener

Change directories into our new one, and we're going to create a few files: src/get-link.ts, src/create-link.ts, and now.json

cd url-shortener mkdir src touch now.json touch src/get-link.ts touch src/create-link.ts

Next we're going to want to add our necessary packages. I like to use yarn for reasons.

yarn add @now/node shortid graphql-request

configuring now

The first configuration we're going to want to do is setup now. I did this late in the project and I could've saved myself a bunch of time by having this first.

We're going to want to setup routes. The routes we're going to use is a POST route for creating new shortened links, and a GET route for getting the link for redirection

Setup your now.json to look like this:

1{
2 "routes": [
3 {
4 "src": "/(.*)",
5 "methods": ["POST"],
6 "dest": "/api/create-link.ts"
7 },
8 {
9 "src": "/(.*)",
10 "methods": ["GET"],
11 "dest": "/api/get-link.ts"
12 }
13 ]
14}

This is going to tell now that when someone does a POST request, to use our create-link file, and when someone does a GET request, to use our get-link file

coding create-link.ts

For this part, we want the ability to create a link. I start with creating links first, because then once I'm done I'll have data to manipulate when we start the get-link logic.

First we want to bring in our dependencies

1import { NowRequest, NowResponse } from '@now/node';
2import shortid from 'shortid';
3import { GraphQLClient } from 'graphql-request';

Next we want to create our mutation for updating our DB. My data structure has an ID and a target URL.

1const newLinkMutation = `
2mutation newLinkMutation($id: String, $urlTarget: String) {
3 insert_sneaky_url(objects: {id: $id, target_url: $urlTarget}) {
4 affected_rows
5 returning {
6 id
7 }
8 }
9}
10`;

And lastly, but not least, we want to create our handler function. This handler function is going to accept a new url in the request body, and then generate an ID for that to store in our DB. In my DB I added a unique enforcement on the ID, but you'd have to create quite a few ID's to start running into issues.

You can also configure a base URL in your environment, or here, whichever works for you. It honestly makes more sense in your environment.

1const handler = (req: NowRequest, res: NowResponse) => {
2 if (req.body) {
3 const { urlTarget } = req.body;
4 const id = shortid.generate();
5 if (urlTarget && id) {
6 const variables = {
7 id,
8 urlTarget
9 };
10
11 const graphQLClient = new GraphQLClient(process.env.graphql_endpoint);
12
13 return graphQLClient.request(newLinkMutation, variables).then(data => {
14 const { affected_rows, returning } = data.insert_sneaky_url;
15 const baseURL = 'https://sneakycrow.dev';
16 if (affected_rows > 0) {
17 return res
18 .status(200)
19 .json({ status: 'success', newUrl: `${baseURL}/${returning[0].id}` });
20 }
21 return res.status(500).json({ status: 'error', message: 'Internal Server Error' });
22 });
23 }
24 }
25 return res.status(400).json({ error: 'This endpoint requires a url target' });
26};
27
28module.exports = handler;

And that's it! We created our function for returning our new URL. You can use now dev to test this. I'll leave it up to you to try and figure out how to test it with a new URL based on the code above ^.

configuring get-link.ts

For this file, we're just going to query our DB based on the ID given, and return a 301 redirect with the target URL.

First, let's bring in our dependencies

1import { NowRequest, NowResponse } from '@now/node';
2import { GraphQLClient } from 'graphql-request';

Next, lets create our query. This will return our target URL based on our ID variable.

1const getLinkQuery = `
2query getLink($id: String) {
3 sneaky_url(where: {id: {_eq: $id}}) {
4 target_url
5 }
6}
7`;

And lastly, let's create our handler function. This will accept a Request and Response. It's going to grab the id based on the path in the request URL. Then it will query our DB and create a 301 redirect with our target link

1const handler = (req: NowRequest, res: NowResponse) => {
2 const linkID = req.url.split('/')[1]; // This should be the ID
3 const queryVariables = {
4 id: linkID
5 };
6
7 const graphQLClient = new GraphQLClient(process.env.graphql_endpoint);
8 if (linkID !== undefined) {
9 graphQLClient.request(getLinkQuery, queryVariables).then(data => {
10 const { target_url } = data.sneaky_url[0];
11 if (target_url) {
12 res.writeHead(301, { Location: target_url });
13 return res.end();
14 }
15 return res.status(500).json({ error: 'Internal Server Error' });
16 });
17 }
18 return res.status(400).json({ error: 'Requires an ID for the link' });
19};
20
21module.exports = handler;

And that's all there is to it! I wrote this tutorial very quickly, if you have any issues at all feel free to email me zach@sneakycrow.dev.


Updated on December 01, 2019