Μάθετε τον κόμβο + MongoDB δημιουργώντας ένα έργο συντόμευσης διευθύνσεων URL

Εάν θέλετε να μάθετε για κάτι, τι καλύτερο τρόπο από το να χτίσετε ένα έργο γύρω από αυτό που θέλετε να μάθετε;

Σε αυτήν την ανάρτηση ιστολογίου, θα μάθουμε για τα MongoDB, Mongoose, Node και άλλες τεχνολογίες δημιουργώντας μια απλή εφαρμογή συντόμευσης URL.

Οι συντομευτές διευθύνσεων URL είναι παντού, από συνδέσμους που μοιράζεστε στο twitter έως δημοφιλείς υπηρεσίες όπως το bit.ly. Αλλά αναρωτηθήκατε ποτέ πώς θα μπορούσατε να δημιουργήσετε έναν γρήγορο συντομευτή URL για τον εαυτό σας;

Επομένως, θα ακολουθήσουμε την πρακτική πρακτική της δημιουργίας ενός συντομευτή διεύθυνσης URL με το MongoDB ως λύση υποστήριξης. Αυτό το έργο θα σας δώσει εμπιστοσύνη στις γνώσεις σας και θα παγιώσει κάθε ιδέα που μαθαίνετε. Ας αρχίσουμε.

Εισαγωγή στο έργο

Θα χρησιμοποιήσουμε αυτήν τη δωρεάν αίθουσα συντόμευσης διευθύνσεων URL από το codedamn για να αποκτήσουμε πρακτική πρακτική και να αξιολογήσουμε την πρόοδό μας καθώς προχωράμε.

Θα χρησιμοποιήσουμε τις ακόλουθες τεχνολογίες:

  • Το Mongoose ως ORM
  • Το MongoDB ως βάση δεδομένων backend
  • Node.js ως το backend
  • Ένα απλό ενσωματωμένο αρχείο JS ως frontend

Θα ολοκληρώσουμε αυτό το έργο σε 7 βήματα, τα οποία θα σας οδηγήσουν από την αρχή έως την ολοκλήρωση. Ας ξεκινήσουμε τα εργαστήρια τώρα.

Μέρος 1: Ρύθμιση του διακομιστή Express

Αρχικά, ρυθμίστε τον διακομιστή Node. Θα χρησιμοποιήσουμε το Express ως πλαίσιο για αυτό το μέρος, καθώς είναι εύκολο να εργαστείς. Εδώ είναι ο σύνδεσμος για αυτό το μέρος.

Μπορούμε να δούμε ότι αυτή είναι μια αρκετά εύκολη άσκηση. Οι μόνες δύο προκλήσεις που πρέπει να ξεπεράσουμε είναι οι εξής:

Η λύση θα μπορούσε να μοιάζει με αυτό:

// 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') })

Απλό και εύκολο. Δημιουργούμε μια άλλη διαδρομή GET χρησιμοποιώντας app.get, και θα πρέπει να γίνει η δουλειά.

Μέρος 2: Ρύθμιση της μηχανής προβολής μας

Τώρα που είμαστε εξοικειωμένοι με την εγκατάσταση του Express, ας ρίξουμε μια ματιά στο .ejsπρότυπο που έχουμε. Εδώ είναι ο σύνδεσμος για αυτό το μέρος.

Η μηχανή EJS σάς επιτρέπει να μεταβιβάσετε μεταβλητές με τον κώδικα Node.js στο HTML σας και να τις επαναλάβετε ή να τις εμφανίσετε προτού στείλετε μια πραγματική απάντηση στο διακομιστή.

Ρίξτε μια γρήγορη ματιά στο views/index.ejsαρχείο. Θα μοιάζει με την εμφάνιση ενός κανονικού αρχείου HTML, εκτός από το ότι μπορείτε να χρησιμοποιήσετε μεταβλητές.

Αυτό είναι το τρέχον index.jsαρχείο μας:

Τώρα, μπορείτε να δείτε ότι στο index.jsαρχείο έχουμε τη γραμμή app.set('view engine', 'ejs'). Λέει στο Express να χρησιμοποιήσει ejsως προεπιλεγμένη μηχανή templating.

Τέλος, δείτε ότι χρησιμοποιούμε το res.render και μεταβιβάζουμε μόνο το όνομα του αρχείου και όχι την πλήρη διαδρομή. Αυτό συμβαίνει επειδή το Express θα κοιτάξει αυτόματα μέσα στο φάκελο προβολών για διαθέσιμα .ejsπρότυπα.

Περνάμε τις μεταβλητές ως το δεύτερο όρισμα, στο οποίο μπορούμε να έχουμε πρόσβαση στο αρχείο EJS. Θα χρησιμοποιήσουμε αυτό το αρχείο αργότερα, αλλά προς το παρόν ας περάσουμε μια γρήγορη πρόκληση.

Για να ολοκληρώσουμε αυτήν την πρόκληση, απλώς πρέπει να αλλάξουμε το όνομα από Mehulοτιδήποτε άλλο.

Για να περάσετε αυτήν την πρόκληση, δείτε index.ejsπρώτα το αρχείο και, στη συνέχεια, ενημερώστε το όνομά σας σε οτιδήποτε άλλο θέλετε. Εδώ είναι μια καλή λύση:

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') })

Μέρος 3: Ρύθμιση του MongoDB

Τώρα που έχουμε λίγη κατανόηση frontend και backend, ας προχωρήσουμε και ρυθμίστε το MongoDB. Εδώ είναι ο σύνδεσμος για αυτό το μέρος.

Θα χρησιμοποιήσουμε το Mongoose για σύνδεση στο MongoDB. Το Mongoose είναι ένα ORM για το MongoDB.

Με απλά λόγια, το MongoDB είναι μια πολύ χαλαρή βάση δεδομένων και επιτρέπει κάθε είδους λειτουργία σε οτιδήποτε.

Αν και είναι καλό για μη δομημένα δεδομένα, τις περισσότερες φορές γνωρίζουμε πραγματικά ποια θα είναι τα δεδομένα (όπως εγγραφές χρηστών ή αρχεία πληρωμών). Έτσι, μπορούμε να ορίσουμε ένα σχήμα για το MongoDB χρησιμοποιώντας το Mongoose. Αυτό κάνει πολλές λειτουργίες εύκολο για εμάς.

Για παράδειγμα, όταν έχουμε ένα σχήμα, μπορούμε να είμαστε σίγουροι ότι η επικύρωση δεδομένων και τυχόν απαραίτητοι έλεγχοι θα χειρίζονται αυτόματα από το Mongoose. Το Mongoose μας δίνει επίσης μια σειρά από βοηθητικές λειτουργίες για να κάνουμε τη ζωή μας πιο εύκολη. Ας το ρυθμίσουμε τώρα.

Για να ολοκληρώσουμε αυτό το μέρος, πρέπει να προσέξουμε τα ακόλουθα σημεία:

  • Το πακέτο Mongoose NPM έχει ήδη εγκατασταθεί για εσάς. Μπορείτε να το κάνετε απευθείας require.
  • Συνδεθείτε στο mongodb://localhost:27017/codedamnURL χρησιμοποιώντας τη mongoose.connectμέθοδο.

Αυτό είναι το τρέχον αρχείο index.js:

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') }) 

Ας συμπληρώσουμε τα κατάλληλα σύμβολα κράτησης θέσης με τον σχετικό κωδικό:

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') }) })

Παρατηρήστε πώς ξεκινάμε τον διακομιστή HTTP μόνο όταν η σύνδεσή μας με το MongoDB είναι ανοιχτή. Αυτό είναι καλό γιατί δεν θέλουμε οι χρήστες να φτάσουν στις διαδρομές μας πριν η βάση δεδομένων μας είναι έτοιμη.

Τελικά χρησιμοποιήσαμε τη db.collectionμέθοδο εδώ για να εισαγάγουμε μια απλή εγγραφή, αλλά θα έχουμε έναν καλύτερο τρόπο σύντομα να αλληλεπιδράσουμε με τη βάση δεδομένων χρησιμοποιώντας μοντέλα Mongoose.

Μέρος 4: Δημιουργία σχήματος Mongoose

Τώρα που είχαμε την πρακτική εμπειρία μας με την εφαρμογή MongoDB στην τελευταία ενότητα, ας σχεδιάσουμε το σχήμα για τη συντόμευση URL. Εδώ είναι ο σύνδεσμος για αυτό το μέρος.

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 }) // ... }) 

Τώρα, αν δούμε ότι το αποτέλεσμα είναι μηδενικό, θα στείλουμε μια κατάσταση 404:

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) })

Αυτό περνά την πρώτη μας πρόκληση. Στη συνέχεια, εάν έχουμε στην πραγματικότητα έναν σύνδεσμο, ας ανακατευθύνουμε τον χρήστη και αυξάνουμε και τον αριθμό κλικ στη βάση δεδομένων.

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) })

Με αυτόν τον τρόπο, μπορούμε να αυξήσουμε και να αποθηκεύσουμε ξανά το αποτέλεσμα στη βάση δεδομένων. Και αυτό πρέπει να περάσει όλες τις προκλήσεις μας.

συμπέρασμα

Συγχαρητήρια! Μόλις δημιουργήσατε έναν πλήρη συντομευτή διεύθυνσης URL μόνοι σας χρησιμοποιώντας το Express + Node + MongoDB. Χαλαρώστε πίσω!

Ο τελικός πηγαίος κώδικας είναι διαθέσιμος στο GitHub.

Εάν έχετε σχόλια σχετικά με αυτό το άρθρο ή τις αίθουσες διδασκαλίας κωδικών, μη διστάσετε να επικοινωνήσετε μαζί μου στο Twitter. Ας συζητήσουμε :)