├── models └── UrlModel.js ├── logger.js ├── package.json ├── .gitignore ├── public └── css │ └── style.css ├── app.js ├── README.md ├── views └── index.html └── routes └── index.js /models/UrlModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const UrlModel = new Schema( 5 | { 6 | original: { type: String }, 7 | shortened: { type: [Number] } 8 | } 9 | ); 10 | 11 | module.exports = mongoose.model('URL', UrlModel); -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | module.exports = (request, response, next) => { 2 | var start = +new Date(); 3 | 4 | var stream = process.stdout; 5 | var url = request.url; 6 | var method = request.method; 7 | 8 | response.on('finish', () => { 9 | var duration = +new Date() - start; 10 | var message = method + ' to ' + url + '\ntook ' + duration + ' ms \n'; 11 | stream.write(message); 12 | 13 | }); 14 | next(); 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url-shortener-microservice", 3 | "version": "0.0.1", 4 | "description": "URL Shortener Microservice app using Node.js, Express.js, and MongoDB.", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "engines": { 10 | "node": "^8.7.0" 11 | }, 12 | "dependencies": { 13 | "dotenv": "^4.0.0", 14 | "express": "^4.16.2", 15 | "mongoose": "^4.12.3", 16 | "valid-url": "^1.0.9" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/maribelduran/url-shortener-microservice.git" 21 | }, 22 | "author": "Maribel Duran" 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /****** Main Styling ******/ 2 | 3 | body { 4 | font-family: 'Roboto', sans-serif; 5 | font-size: 16px; 6 | color: #222; 7 | text-align: center; 8 | } 9 | 10 | .container { 11 | padding: 0; 12 | width: 100%; 13 | margin-top: 40px; 14 | text-align: left; 15 | } 16 | 17 | .well{ 18 | margin: auto; 19 | width: 70%; 20 | border-style: solid; 21 | padding: 10px; 22 | word-wrap: break-word; 23 | } 24 | 25 | h1, h2{ 26 | border-bottom: 1px solid #eaecef 27 | } 28 | 29 | code { 30 | padding: 2px 4px; 31 | font-size: 90%; 32 | color: #c7254e; 33 | background-color: #f9f2f4; 34 | border-radius: 4px; 35 | } 36 | 37 | footer { 38 | margin-top: 20px; 39 | text-align: center; 40 | } 41 | 42 | ol { 43 | list-style-position: inside; 44 | } 45 | ul { 46 | list-style-type: none; 47 | } 48 | 49 | a { 50 | color: #2574A9; 51 | text-decoration: none; 52 | 53 | } 54 | 55 | a:hover{ 56 | color: #000; 57 | 58 | } 59 | 60 | 61 | /****** Logo Div Styling ******/ 62 | 63 | img { 64 | margin: 20px auto 0 auto; 65 | display: block; 66 | } 67 | 68 | /****** FontAwesome Styling ******/ 69 | 70 | .fa{ 71 | font-size: 22px !important; 72 | 73 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Require the modules we need 4 | const express = require('express'); 5 | const mongoose = require('mongoose'); 6 | const bodyParser = require('body-parser'); 7 | const path = require("path"); 8 | const url = require('url'); 9 | const logger = require('./logger'); 10 | const routes = require('./routes'); 11 | require('dotenv').config(); 12 | 13 | mongoose.connect(process.env.MONGO_CONNECTION_STRING, 14 | { useMongoClient:true, 15 | promiseLibrary: global.Promise } 16 | ); 17 | 18 | // Set up express 19 | const app = express(); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({extended:true})); 22 | app.use('/public', express.static(path.join(__dirname, 'public'))); 23 | 24 | app.use(routes) 25 | 26 | // Processes homepage request 27 | app.get('/', (req, res) => { 28 | res.sendFile(__dirname + '/views/index.html'); 29 | }); 30 | 31 | // Respond not found to all the wrong routes 32 | app.use((req, res, next) => { 33 | res.status(404); 34 | res.type('txt').send('Not found'); 35 | }); 36 | 37 | // Error Middleware 38 | app.use((err, req, res, next) => { 39 | if(err) { 40 | res.status(err.status || 500) 41 | .type('txt') 42 | .send(err.message || 'SERVER ERROR'); 43 | } 44 | }); 45 | 46 | //Listen for requests 47 | const server = app.listen(process.env.PORT || 3000, () => { 48 | const port = server.address().port; 49 | console.log('URL Shortener Microservice app is listening on port ', port); 50 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # URL Shortener Microservice :blue_heart: 2 | 3 | This service accepts a URL as a parameter and will check whether it follows the valid https://www.google.com format. If the URL is valid, it will return both the original URL and shortened URL in the JSON response. If it is not valid, the JSON response will contain an error instead. When you visit that shortened URL, it will redirect you to the original link. 4 | 5 | You can test it at https://url-shortener-microsrvc.herokuapp.com 6 | 7 | A Full Stack Javascript application built using [MongoDB](https://www.mongodb.org/), [Node.js](https://nodejs.org/) and [Express](https://expressjs.com/). An API Project for FreeCodeCamp. 8 | 9 | ## Example Creation Usage 10 | 11 | Pass the URL to path https://url-shortener-microsrvc.herokuapp.com/new/[URL] as below: 12 | 13 | ### Valid URL example 14 | ``` 15 | https://url-shortener-microsrvc.herokuapp.com/new/https://www.github.com 16 | ``` 17 | ### Invalid URL example (missing protocol) 18 | ``` 19 | https://url-shortener-microsrvc.herokuapp.com/new/www.github.com 20 | ``` 21 | 22 | ## Example Creation Output 23 | 24 | ### Valid URL example JSON response output 25 | ```javascript 26 | { 27 | original_url: "https://www.github.com", 28 | shortened_url: "https://url-shortener-microsrvc.herokuapp.com/29183" 29 | } 30 | ``` 31 | 32 | ## Usage: 33 | Visiting the shortened URL: https://url-shortener-microsrvc.herokuapp.com/29183 34 | will redirect to: https://www.github.com 35 | 36 | 37 | ## To Run Project Locally 38 | 39 | ### Prerequisites 40 | In order to run this project locally, you should have the following installed: 41 | 42 | - [Node.js](https://nodejs.org/) 43 | - [NPM](https://www.npmjs.com//) 44 | - [MongoDB](https://www.mongodb.org/) 45 | 46 | ### Installation & Startup 47 | 1. Fork this repo 48 | 2. Clone the fork 49 | 3. Install Dependencies: `$ npm install` 50 | 4. Run your MongoDB server: `$ mongod --port 27017 --dbpath=./data` 51 | 52 | *Note*: Your mongoDB is now running at: mongodb://localhost:27017/ 53 | 5. Add a .env file to your project's root directory and set MONGO_CONNECTION_STRING to mongodb://localhost:27017/ 54 | ``` JavaScript 55 | MONGO_CONNECTION_STRING=mongodb://localhost:27017/ 56 | ``` 57 | 58 | 59 | 60 | 61 | 62 | 6. Start the Server: `$ node app.js` 63 | 7. Visit http://localhost:3000/ 64 | 65 | Enjoy! :blue_heart: 66 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | URL Shortener Microservice 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

URL Shortener Microservice 💙

17 |

18 | This service accepts a URL as a parameter and will check whether it follows the valid http://www.example.com format. 19 | If the URL is valid, it will return both the original URL and shortened URL in the JSON response. If it is not, 20 | the JSON response will contain an error instead. When you visit that shortened URL, it will redirect you to the original link. 21 |

22 | 23 |

Example Creation Usage

24 |

Pass the URL to path https://url-shortener-microsrvc.herokuapp.com/new/[URL] as below:

25 | 26 |

Valid URL example

27 | https://url-shortener-microsrvc.herokuapp.com/new/https://www.github.com 28 |
29 | 30 |

Example Creation Output

31 |

Valid URL example JSON response

32 | { 33 |
original_url: "https://www.github.com", 34 |
35 | shortened_url: "https://url-shortener-microsrvc.herokuapp.com/29183" 36 |
37 | } 38 |
39 | 40 |

Usage:

41 |

Visiting the shortened URL: https://url-shortener-microsrvc.herokuapp.com/29183 42 | will redirect to:https://www.github.com

43 | 44 |

Enjoy! 💙

45 | 46 |
47 | 55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const path = require('path'); 3 | const validUrl = require('valid-url'); 4 | const URL = require('../models/UrlModel.js') 5 | 6 | //Creates a 5 digit random number between 10000 and 99999 7 | const random5DigitNum = () => { 8 | return Math.floor(Math.random() * 90000) + 10000; 9 | } 10 | 11 | //Checks if the url is valid before trying to access routes that access the database 12 | router.use('/new/*', (req, res, next) => { 13 | const url = req.params[0]; 14 | const response = { error: "Your url is invalid or in the wrong format. Verify that a valid protocol (http or https) is part of the url." }; 15 | if (validUrl.isUri(url)) { 16 | req.uri = url; 17 | next(); 18 | }else { 19 | res.json(response); 20 | } 21 | }); 22 | 23 | router.route('/new/*') 24 | //currently not working with PUT or POST 25 | .get((req, res) => { 26 | const shortenedUrl = random5DigitNum(); 27 | URL.findOneAndUpdate( 28 | {original: req.uri}, // filter 29 | {$push: { shortened: shortenedUrl} }, // update 30 | {upsert: true, new: true, runValidators: true}, // options 31 | function (err, doc) { // callback 32 | if (err) { 33 | res.status(500).send(err); 34 | }else { 35 | let reqBaseUrl = req.protocol + '://' + req.get('host'); 36 | let response = { 37 | original_url: doc.original, 38 | shortened_url: `${reqBaseUrl}/${shortenedUrl}` 39 | }; 40 | res.json(response); 41 | } 42 | } 43 | ); 44 | }); 45 | 46 | //Checks that the passed shortened url is a digit value before querying the database. 47 | router.use('/:shortenedURL/', (req, res, next) => { 48 | const isNumber = /^\d+$/.test(req.params.shortenedURL); 49 | const response = { error: "The shortened url is in the wrong format. Verify that passed shortened URL is a digit value." }; 50 | if (isNumber){ 51 | next(); 52 | }else { 53 | res.json(response); 54 | } 55 | }); 56 | 57 | router.route('/:shortenedURL/') 58 | .get((req, res) => { 59 | const shortenedURL = req.params.shortenedURL; 60 | var queryDoc = { shortened: shortenedURL}; 61 | var select = 'original'; 62 | URL.findOne(queryDoc, select, (err, url) => { 63 | if (err) { 64 | res.status(500).send(err); 65 | } else if (url) { 66 | res.redirect(301, url.original); 67 | } else { 68 | const response = { error: "This shortened url is not in the database" }; 69 | res.json(response); 70 | } 71 | }); 72 | }); 73 | 74 | module.exports = router; --------------------------------------------------------------------------------