Url Shrink

This project it's done and published at my Github and even though it's not fully adapted to be here on my website I'll talk about how i created it.

Used dependecies:
- Fastify
- @fastify/cors
- dotenv
- zod
- knex
- sqlite3
- nanoid

Fastify // @fastify/cors
Instead of using Express I decided to use Fastify for I'm currently coursing a NodeJS Expert course and Fastify and it's own Cors it was introduced there.
So i thought it was better for my understanding to use it, also i noticed that it's better maintained since the team behind it is really active.

nanoid
Nanoid is a tool to created random ID the size you want it. Very practical and simple to use.
Here's the documentation.

zod
This is a great application to pre define some inputs, since JS doesn't accepts basically everything this tool actually makes it pre-defined and well documented.
You'll notice for example on the POST code down bellow that a Schema is created before the code does it's thing using Zod defining that the website is a string.

dotenv // knex // sqlite3
I honestly think they're extremely comon so I won't be going into specifics here.


Backend Structure
The backend structure is define by 3 main routes and 1 migration:
1 - Listing all websites along how many times they were submited using a GET method.
2 - Posting a website to get the NanoID for it using a POST method.
3 - Redirecting to the website required when used the NanoID using a GET method.

Migration
The migration was created with a unique ID, a NanoID and the website:

exports.up = async function(knex) {
  await knex.schema.createTable('db-url', (table) => {
    table.uuid('id').primary()
    table.uuid('nanoidWebsite')
    table.text('website').notNullable()
  });
};


1 - Listing:
Using a simple knex query I was able to bring the websites on the database called 'db-url' grouping and counting by the website.
app.get('/', async () => {
  //return await knex('db-url').select('*');
  //console.log(websites);
  const websites = await knex('db-url').select('website').groupBy('website').count('website as howMany');
  return websites;
});


2 - Posting:
The POST method is quite simple but the one thing that i found important to do, and still needs some improvement is to check if the website was already used and reuse the same NanoID for it.
I said that still needs improvement because this logic doesn't check everything, for example, 'www.google.com' is diferent from 'https://www.google.com', so this checking logic still have to be created.
Also it returns the ShortID to the frontend.
app.post('/', async (request, reply) => {
  const createWebsiteNanoIdBodySchema = z.object({
    website: z.string(),
  });

  const { website } = createWebsiteNanoIdBodySchema.parse(
    request.body,
  );

  let shortid;
  const checkWebsiteExists = await knex('db-url').select('nanoidWebsite').where({website: website}).first();

  if (checkWebsiteExists) {
    shortid = checkWebsiteExists.nanoidWebsite;
  } else {
    await knex('db-url')
    .insert({
      id: crypto.randomUUID(),
      nanoidWebsite: nanoid(8),
      website,
    });
  };
  
  //console.log(shortid);

  return reply.status(201).send({ shortUrl: shortid });

});


3 - Redirecting:
It's as simple as a knex query to get the website with the respective NanoID and redirect to it.
app.get('/:nanoidInput', async (request, reply) => {

  const { nanoidInput } = request.params;

  const record = await knex('db-url').select('website').where({nanoidWebsite: nanoidInput}).first();
  
  if (record) {
    return reply.redirect(record.website.startsWith("http") ? record.website : `http://${record.website}`);
  } else {
    reply.status(404).send({error: 'url not found'});
  }

});



This a great example of data manipulation using Knex with a good Frontend comunication and it was super fun to do so!
I hope you've enjoyed the reading for i sure enjoyed writing and creating this project!


Don't forget to check the links!
by Thales Fornazari