Pelajari Node + MongoDB dengan Membuat Projek Pemendek URL

Sekiranya anda ingin belajar tentang sesuatu, cara apa yang lebih baik daripada membina projek di sekitar perkara yang ingin anda pelajari?

Dalam catatan blog ini, kita akan belajar mengenai MongoDB, Mongoose, Node, dan teknologi lain dengan membina aplikasi pemendek URL mudah.

Pemendek URL ada di mana-mana, dari pautan yang anda kongsi di twitter hingga perkhidmatan popular seperti bit.ly. Tetapi adakah anda pernah terfikir bagaimana anda boleh membuat pemendek URL cepat untuk diri anda sendiri?

Oleh itu, kita akan menjalani praktik praktikal untuk membina pemendek URL dengan MongoDB sebagai penyelesaian backend kami. Projek ini akan memberi anda keyakinan terhadap pengetahuan anda dan memantapkan setiap konsep yang anda pelajari. Mari kita mulakan.

Pengenalan projek

Kami akan menggunakan bilik darjah pemendek URL percuma ini dari codedamn untuk mendapatkan latihan langsung dan menilai kemajuan kami semasa kami meneruskan.

Kami akan menggunakan teknologi berikut:

  • Mongoose sebagai ORM
  • MongoDB sebagai pangkalan data backend
  • Node.js sebagai backend
  • Fail JS terbenam sederhana sebagai frontend

Kami akan menyelesaikan projek ini dalam 7 langkah, yang akan membawa anda dari awal hingga selesai. Mari mulakan makmal sekarang.

Bahagian 1: Menyiapkan pelayan Express

Pertama mari sediakan pelayan Node kami. Kami akan menggunakan Express sebagai kerangka kerja untuk bahagian ini kerana senang digunakan. Inilah pautan ke bahagian ini.

Kita dapat melihat bahawa ini adalah latihan yang cukup mudah. Dua cabaran yang harus kita atasi adalah berikut:

Penyelesaiannya boleh seperti ini:

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

Ringkas dan senang. Kami membuat laluan GET lain menggunakan app.get, dan ia harus menyelesaikan tugas.

Bahagian 2: Menyiapkan mesin pandangan kami

Sekarang kita sudah biasa dengan pemasangan Express, mari kita lihat .ejstemplat yang kita ada. Inilah pautan ke bahagian ini.

Enjin EJS membolehkan anda menyampaikan pemboleh ubah dengan kod Node.js ke HTML anda dan mengulangi atau memaparkannya sebelum anda menghantar respons sebenar ke pelayan.

Lihat views/index.ejsfailnya dengan cepat . Ia akan serupa dengan rupa fail HTML biasa, kecuali anda boleh menggunakan pemboleh ubah.

Inilah index.jsfail semasa kami :

Sekarang, anda dapat melihat bahawa dalam index.jsfail kita mempunyai garis app.set('view engine', 'ejs'). Ia memberitahu Express untuk digunakan ejssebagai mesin templat lalai.

Akhirnya, lihat bahawa kami menggunakan res.render dan hanya meneruskan nama fail, bukan jalan penuh. Ini kerana Express akan secara automatik melihat ke dalam folder pandangan untuk .ejstemplat yang tersedia .

Kami menyampaikan pemboleh ubah sebagai argumen kedua, yang kemudian kami dapat mengakses dalam fail EJS. Kami akan menggunakan fail ini kemudian, tetapi buat masa ini mari kita melalui cabaran pantas.

Untuk menyelesaikan cabaran ini, kita hanya perlu menukar nama dari yang Mehullain.

Untuk mengatasi cabaran ini, lihat index.ejsfail terlebih dahulu dan kemudian kemas kini nama anda ke perkara lain yang anda suka. Inilah penyelesaian yang baik:

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

Bahagian 3: Menubuhkan MongoDB

Sekarang kita mempunyai sedikit pemahaman frontend dan backend, mari maju dan siapkan MongoDB. Inilah pautan ke bahagian ini.

Kami akan menggunakan Mongoose untuk menyambung ke MongoDB. Mongoose adalah ORM untuk MongoDB.

Secara ringkasnya, MongoDB adalah pangkalan data yang sangat longgar , dan ia membolehkan semua jenis operasi pada apa sahaja.

Walaupun baik untuk data yang tidak terstruktur, sebagian besar waktu kita benar-benar mengetahui apa data tersebut (seperti catatan pengguna atau catatan pembayaran). Oleh itu, kita dapat menentukan skema untuk MongoDB menggunakan Mongoose. Ini menjadikan banyak fungsi mudah bagi kita.

Sebagai contoh, setelah kita mempunyai skema, kita dapat yakin bahawa pengesahan data dan pemeriksaan yang diperlukan akan dikendalikan oleh Mongoose secara automatik. Mongoose juga memberi kita banyak fungsi pembantu untuk menjadikan hidup kita lebih mudah. Mari sekarang siapkan.

Untuk menyelesaikan bahagian ini, kita harus menjaga perkara berikut:

  • Pakej Mongoose NPM telah dipasang untuk anda. Anda boleh langsung require.
  • Sambungkan ke mongodb://localhost:27017/codedamnURL menggunakan mongoose.connectkaedah.

Berikut fail index.js kami:

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

Mari isi tempat letak yang sesuai dengan kod yang berkaitan:

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

Perhatikan bagaimana kita memulakan pelayan HTTP kita hanya apabila hubungan kita dengan MongoDB terbuka. Ini baik-baik saja kerana kami tidak mahu pengguna memasuki laluan kami sebelum pangkalan data kami siap.

Kami akhirnya menggunakan db.collectionkaedah di sini untuk memasukkan catatan ringkas, tetapi kami akan mempunyai cara yang lebih baik segera untuk berinteraksi dengan pangkalan data menggunakan model Mongoose.

Bahagian 4: Menyiapkan skema Mongoose

Sekarang setelah kami mendapat pengalaman langsung dengan pelaksanaan MongoDB di bahagian terakhir, mari buat skema pemendek URL kami. Inilah pautan untuk bahagian ini.

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 URLShort URLClicks

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