Aprenda Node + MongoDB creando un proyecto de acortador de URL

Si quieres aprender sobre algo, ¿qué mejor manera que construyendo un proyecto en torno a lo que quieres aprender?

En esta publicación de blog, aprenderemos sobre MongoDB, Mongoose, Node y otras tecnologías mediante la creación de una aplicación de acortador de URL simple.

Los acortadores de URL están en todas partes, desde los enlaces que comparte en Twitter hasta servicios populares como bit.ly. Pero, ¿se ha preguntado alguna vez cómo podría crear un acortador de URL rápido para usted?

Así que pasaremos por la práctica práctica de crear un acortador de URL con MongoDB como nuestra solución de backend. Este proyecto le dará confianza en su conocimiento y solidificará cada concepto que aprenda. Empecemos.

Introducción al proyecto

Usaremos este aula de acortador de URL gratuito de codedamn para practicar y evaluar nuestro progreso a medida que avanzamos.

Usaremos las siguientes tecnologías:

  • Mangosta como ORM
  • MongoDB como base de datos de backend
  • Node.js como backend
  • Un simple archivo JS incrustado como interfaz

Completaremos este proyecto en 7 pasos, que lo llevarán desde el principio hasta el final. Comencemos los laboratorios ahora.

Parte 1: Configurar el servidor Express

Primero configuremos nuestro servidor de nodo. Usaremos Express como marco para esta parte, ya que es fácil trabajar con él. Aquí está el enlace a esta parte.

Podemos ver que este es un ejercicio relativamente fácil. Los únicos dos desafíos que tenemos que superar son los siguientes:

La solución podría verse así:

// Initialize express server on PORT 1337 const express = require('express') const app = express() app.get('/', (req, res) => { res.send('Hello World! - from codedamn') }) app.get('/short', (req, res) => { res.send('Hello from short') }) app.listen(process.env.PUBLIC_PORT, () => { console.log('Server started') })

Simple y fácil. Creamos otra ruta GET usando app.get, y debería hacer el trabajo.

Parte 2: Configurar nuestro motor de visualización

Ahora que estamos familiarizados con la instalación Express, echemos un vistazo a la .ejsplantilla que tenemos. Aquí está el enlace a esta parte.

El motor EJS le permite pasar variables con el código Node.js a su HTML e iterarlas o mostrarlas antes de enviar una respuesta real al servidor.

Eche un vistazo rápido al views/index.ejsarchivo. Se verá similar a cómo se ve un archivo HTML normal, excepto que puede usar variables.

Aquí está nuestro index.jsarchivo actual :

Ahora, puedes ver que en el index.jsarchivo tenemos la línea app.set('view engine', 'ejs'). Le dice a Express que lo use ejscomo su motor de plantillas predeterminado.

Finalmente, vea que estamos usando res.render y solo pasamos el nombre del archivo, no la ruta completa. Esto se debe a que Express buscará automáticamente dentro de la carpeta de vistas las .ejsplantillas disponibles .

Pasamos variables como segundo argumento, al que luego podemos acceder en el archivo EJS. Usaremos este archivo más adelante, pero por ahora veamos un desafío rápido.

Para completar este desafío, solo necesitamos cambiar el nombre de Mehula cualquier otra cosa.

Para aprobar este desafío, index.ejsprimero vea el archivo y luego actualice su nombre a cualquier otra cosa que desee. He aquí una buena solución:

const express = require('express') const app = express() app.set('view engine', 'ejs') app.get('/', (req, res) => { res.render('index', { myVariable: 'My name is John!' }) }) app.listen(process.env.PUBLIC_PORT, () => { console.log('Server started') })

Parte 3: Configurar MongoDB

Ahora que tenemos un poco de comprensión de frontend y backend, sigamos adelante y configuremos MongoDB. Aquí está el enlace a esta parte.

Usaremos Mongoose para conectarnos a MongoDB. Mongoose es un ORM para MongoDB.

Simplemente hablando, MongoDB es una base de datos muy flexible y permite todo tipo de operaciones en cualquier cosa.

Si bien es bueno para datos no estructurados, la mayoría de las veces somos conscientes de cuáles serán los datos (como registros de usuarios o registros de pagos). Por lo tanto, podemos definir un esquema para MongoDB usando Mongoose. Esto nos facilita muchas funciones.

Por ejemplo, una vez que tengamos un esquema, podemos estar seguros de que Mongoose manejará automáticamente la validación de datos y cualquier verificación necesaria. Mongoose también nos brinda un montón de funciones auxiliares para hacernos la vida más fácil. Ahora configurémoslo.

Para completar esta parte, tenemos que cuidar los siguientes puntos:

  • El paquete Mongoose NPM ya se ha instalado. Puedes requirehacerlo directamente .
  • Conéctese a la mongodb://localhost:27017/codedamnURL utilizando el mongoose.connectmétodo.

Aquí está nuestro archivo index.js actual:

const express = require('express') const app = express() const mongoose = require('mongoose') app.set('view engine', 'ejs') app.get('/', (req, res) => { res.render('index') }) app.post('/short', (req, res) => { const db = mongoose.connection.db // insert the record in 'test' collection res.json({ ok: 1 }) }) // Setup your mongodb connection here // mongoose.connect(...) // Wait for mongodb connection before server starts app.listen(process.env.PUBLIC_PORT, () => { console.log('Server started') }) 

Completemos los marcadores de posición apropiados con el código relevante:

const express = require('express') const app = express() const mongoose = require('mongoose') app.set('view engine', 'ejs') app.get('/', (req, res) => { res.render('index') }) app.post('/short', (req, res) => { const db = mongoose.connection.db // insert the record in 'test' collection db.collection('test').insertOne({ testCompleted: 1 }) res.json({ ok: 1 }) }) // Setup your mongodb connection here mongoose.connect('mongodb://localhost/codedamn', { useNewUrlParser: true, useUnifiedTopology: true }) mongoose.connection.on('open', () => { // Wait for mongodb connection before server starts app.listen(process.env.PUBLIC_PORT, () => { console.log('Server started') }) })

Observe cómo iniciamos nuestro servidor HTTP solo cuando nuestra conexión con MongoDB está abierta. Esto está bien porque no queremos que los usuarios accedan a nuestras rutas antes de que nuestra base de datos esté lista.

Finalmente usamos el db.collectionmétodo aquí para insertar un registro simple, pero pronto tendremos una mejor manera de interactuar con la base de datos usando modelos de Mongoose.

Parte 4: Configurar un esquema de Mongoose

Ahora que hemos tenido nuestra experiencia práctica con la implementación de MongoDB en la última sección, dibujemos el esquema para nuestro acortador de URL. Aquí está el enlace para esta parte.

A Mongoose schema allows us to interact with the Mongo collections in an abstract way. Mongoose's rich documents also expose helper functions like .save which are enough to perform a full DB query to update changes in your document.

Here's how our schema for the URL shortener will look:

const mongoose = require('mongoose') const shortId = require('shortid') const shortUrlSchema = new mongoose.Schema({ full: { type: String, required: true }, short: { type: String, required: true, default: shortId.generate }, clicks: { type: Number, required: true, default: 0 } }) module.exports = mongoose.model('ShortUrl', shortUrlSchema)

We'll store this file in the models/url.js file. Once we have the schema, we can pass this part of the exercise. We have to do the following two things:

  1. Create this model in the models/url.js file. (We did that.)
  2. A POST request to /short should add something to the database to this model.

In order to do that, we can generate a new record using the following code:

app.post('/short', async (req, res) => { // insert the record using the model const record = new ShortURL({ full: 'test' }) await record.save() res.json({ ok: 1 }) })

You'll see that we can omit the clicks and short field because they already have a default value in the schema. This means Mongoose will populate them automatically when the query runs.

Our final index.js file to pass this challenge should look like this:

const express = require('express') const app = express() const mongoose = require('mongoose') // import the model here const ShortURL = require('./models/url') app.set('view engine', 'ejs') app.get('/', (req, res) => { res.render('index', { myVariable: 'My name is John!' }) }) app.post('/short', async (req, res) => { // insert the record using the model const record = new ShortURL({ full: 'test' }) await record.save() res.json({ ok: 1 }) }) // Setup your mongodb connection here mongoose.connect('mongodb://localhost/codedamn') mongoose.connection.on('open', () => { // Wait for mongodb connection before server starts app.listen(process.env.PUBLIC_PORT, () => { console.log('Server started') }) })

Part 5: Linking the frontend, backend, + MongoDB

Now that we have a handle on the backend part, let’s get back to the frontend and setup our webpage. There we can use the Shrink button to actually add some records to the database. Here's the link to this part.

If you look inside the views/index.ejs file, you’ll see that we have already passed the form data on the backend /short route. But right now we are not grabbing it.

  • You can see that there’s a new line called app.use(express.urlencoded({ extended: false })) on line 8, which allows us to read the response of the user from the form.
  • In the index.ejs file, you can see that we set name=”fullURL” which is how we can receive the URL on the backend.

Here's our index.ejs file:

       codedamn URL Shortner Project 

URL Shrinker

URL Shrink This! { %>
Full URL Short URL Clicks

This is a simple challenge, because we just have to put this code in to complete it:

app.use(express.urlencoded({ extended: false })) app.post('/short', async (req, res) => { // Grab the fullUrl parameter from the req.body const fullUrl = req.body.fullUrl console.log('URL requested: ', fullUrl) // insert and wait for the record to be inserted using the model const record = new ShortURL({ full: fullUrl }) await record.save() res.redirect('/') })

First of all, we grab the sent URL by HTML using the req.body.fullUrl. To enable this, we also have app.use(express.urlencoded({ extended: false })) which allows us to get the form data.

Then we create and save our record just like we did the last time. Finally, we redirect the user back to the homepage so that the user can see the new links.

Tip: You can make this application more interesting by performing an Ajax request to the backend API instead of typical form submission. But we'll leave it here as it focuses more on MongoDB + Node setup instead of JavaScript.

Part 6: Displaying short URLs on the frontend

Now that we’re storing shortened URLs in MongoDB, let’s go ahead and show them on the frontend as well.

Remember our variables passed down to the ejs template from before? Now we’ll be using them.

The template loop for ejs has been done for you in the index.ejs file (you can see that loop above). However, we have to write the Mongoose query to extract the data in this section.

If we see the template, we'll see that in index.js we have the following code:

app.get('/', (req, res) => { const allData = [] // write a mongoose query to get all URLs from here res.render('index', { shortUrls: allData }) }) 

We already have a model defined with us to query data from Mongoose. Let's use it to get everything we need.

Here's our solution file:

const express = require('express') const app = express() const mongoose = require('mongoose') // import the model here const ShortURL = require('./models/url') app.set('view engine', 'ejs') app.use(express.urlencoded({ extended: false })) app.get('/', async (req, res) => { const allData = await ShortURL.find() res.render('index', { shortUrls: allData }) }) app.post('/short', async (req, res) => { // Grab the fullUrl parameter from the req.body const fullUrl = req.body.fullUrl console.log('URL requested: ', fullUrl) // insert and wait for the record to be inserted using the model const record = new ShortURL({ full: fullUrl }) await record.save() res.redirect('/') }) // Setup your mongodb connection here mongoose.connect('mongodb://localhost/codedamn', { useNewUrlParser: true, useUnifiedTopology: true }) mongoose.connection.on('open', async () => { // Wait for mongodb connection before server starts // Just 2 URLs for testing purpose await ShortURL.create({ full: '//google.com' }) await ShortURL.create({ full: '//codedamn.com' }) app.listen(process.env.PUBLIC_PORT, () => { console.log('Server started') }) })

You can see that it was as easy as doing await ShortURL.find() in the allData variable. The next part is where things get a bit tricky.

Part 7: Making the redirection work

We’re almost done! We have the full URL and short URL stored in the database now, and we show them on the frontend too.

But you’ll notice that the redirection does not work right now and we get an Express error.

Let’s fix that. You can see in the index.js file there’s a new dynamic route added at the end which handles these redirects:

app.get('/:shortid', async (req, res) => { // grab the :shortid param const shortid = '' // perform the mongoose call to find the long URL // if null, set status to 404 (res.sendStatus(404)) // if not null, increment the click count in database // redirect the user to original link })

Our challenges for this part looks like this:

Alright. First things first, we have to extract out the full URL when we visit a short URL. Here's how we'll do that:

app.get('/:shortid', async (req, res) => { // grab the :shortid param const shortid = req.params.shortid // perform the mongoose call to find the long URL const rec = await ShortURL.findOne({ short: shortid }) // ... }) 

Now, if we see that our result is null, we'll send a 404 status:

app.get('/:shortid', async (req, res) => { // grab the :shortid param const shortid = req.params.shortid // perform the mongoose call to find the long URL const rec = await ShortURL.findOne({ short: shortid }) // if null, set status to 404 (res.sendStatus(404)) if (!rec) return res.sendStatus(404) res.sendStatus(200) })

This passes our first challenge. Next, if we in fact have a link, let's redirect the user and increment the click count too in the database.

app.get('/:shortid', async (req, res) => { // grab the :shortid param const shortid = req.params.shortid // perform the mongoose call to find the long URL const rec = await ShortURL.findOne({ short: shortid }) // if null, set status to 404 (res.sendStatus(404)) if (!rec) return res.sendStatus(404) // if not null, increment the click count in database rec.clicks++ await rec.save() // redirect the user to original link res.redirect(rec.full) })

This way, we can increment and store the result in the database again. And that should pass all of our challenges.

Conclusion

Congratulations! You just built a full working URL shortener by yourself using Express + Node + MongoDB. Give yourself a pat on back!

The final source code is available on GitHub.

If you have any feedback on this article or codedamn classrooms, feel free to reach out to me on Twitter. Let's discuss :)