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!