用Node.js构建一个URL缩短器

718 阅读5分钟

bit.lycutt.ly这样的URL缩短器是非常流行的。在这篇文章中,我们将通过建立一个缩短提供给它的URL的API服务来创建一个类似的工具。

Demonstration Of Get Request URL Shortener API Service

在这个项目中,我们将使用MongoDBNode.js,所以你应该有关于它们的基本知识来学习这个教程。

在Node.js中规划URL缩短器的构建过程

让我们首先规划一下构建过程,这是很简单的。对于传入我们的API的每个URL,我们将生成一个唯一的ID,并用它创建一个短URL。然后,长URL、短URL和唯一ID将被存储在数据库中。

当用户向短网址发送一个GET ,该网址将在数据库中被搜索到,用户将被重定向到相应的原始网址。听起来很复杂?别担心,我们会涵盖你需要知道的一切。

用MongoDB初始化应用程序和安装依赖性

首先,我们将需要一个数据库。因为我们将使用MongoDB,我们需要一个MongoDB SRV URI。你可以从这个链接创建一个数据库。我们的下一步是用NPM初始化项目文件夹。

让我们使用项目目录下的命令npm init 来初始化。一旦项目被初始化,我们将安装所需的依赖项。我们需要的依赖性是。

  • dotenv :这个包从一个叫做.env 的文件中加载环境变量,然后再加载到 。process.env
  • express :这是一个用于Node.js的最小而灵活的Web应用框架
  • mongoose: 这是一个用于Node.js的MongoDB对象建模工具
  • shortid :这个包使我们能够为我们的URL生成短的ID。

我们唯一需要的开发者依赖性是nodemonnodemon 工具是一个简单的工具,可以在文件发生变化时自动重新启动Node.js服务器。

现在,让我们来安装这些依赖项。为了安装我们的应用程序中需要的依赖项,我们将使用命令npm i dotenv express mongoose shortid ,在依赖项安装完毕后,我们将用npm i -D nodemon 安装开发者依赖项。

让我们使用Express在我们的app.js 文件中创建我们的服务器。为了建立一个Express服务器,我们需要将Express包导入到app.js 文件中。一旦软件包被导入,初始化并将其存储到一个名为app 的变量中。

现在,使用可用的listen 函数来创建服务器。这里有一个例子。

const Express = require('Express');
const app = Express();

// Server Setup
const PORT = 3333;
app.listen(PORT, () => {
  console.log(`Server is running at PORT ${PORT}`);
});

我使用了端口3333 来运行服务器。Express中的listen 方法启动了一个UNIX套接字,并在一个给定的端口中监听连接。

现在,在config 文件夹中创建一个.env 文件,以存储 MongoDB SRV URI 和基本 URL。基本URL现在将是你的本地主机服务器位置。下面是我的.env 文件代码。

MONGO_URI=mongodb+srv://nemo:YourPasswordHere@cluster0.mkws3.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
BASE=http://localhost:3333

记住用你的数据库密码改变MongoDB URI中的<password> 字段。

将数据库连接到应用程序

现在,我们将把数据库连接到应用程序。为此,将mongoosedotenv 依赖项导入你的db.js 文件,该文件位于config 文件夹内。

const mongoose = require('mongoose');
require('dotenv').config({ path: './.env' });

path 的对象键是在dotenv 的配置里面传递的,因为.env 文件不在根目录下。我们正在通过这个传递.env 文件的位置。

现在在config 文件夹内的一个名为db.js 的文件中创建一个名为connectDB 的异步函数。在这篇文章中,我将使用async/await

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('Database Connected');
  } catch (err) {
    console.error(err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

try 块中,我们等待mongoose 与给定的MongoDB URI连接。mongoose.connect 方法的第一个参数是MongoDB SRV URI。注意在第二个参数中传递了两个键值对,以消除控制台的警告。让我们来理解这两个键值参数的含义。

  • useNewUrlParser: true :MongoDB底层驱动程序已经废弃了当前的连接字符串解析器。这就是为什么它增加了一个新的标志。如果连接遇到新的字符串解析器的任何问题,它可以退回到旧的解析器
  • useUnifiedTopology: true: 默认设置为false 。在这里,它被设置为true ,以便可以使用MongoDB驱动的新连接管理引擎

如果在catch 语句中出现任何错误,我们将在控制台记录错误并以process.exit(1) 退出。最后,我们用module.exports 输出该函数。

现在,用const connectDB = require('./config/db');db.js 文件导入到app.js 文件中,用connectDB() 调用connectDB 函数。

在MongoDB中创建mongoose模式

我们将使用mongoose模式来确定数据在MongoDB中的存储方式。从本质上讲,mongoose模式是数据的一个模型。让我们在models 文件夹内创建一个名为Url.js 的文件。在这里导入mongoose ,然后使用mongoose.Schema 构造函数来创建模式。

const mongoose = require('mongoose');

const UrlSchema = new mongoose.Schema({
  urlId: {
    type: String,
    required: true,
  },
  origUrl: {
    type: String,
    required: true,
  },
  shortUrl: {
    type: String,
    required: true,
  },
  clicks: {
    type: Number,
    required: true,
    default: 0,
  },
  date: {
    type: String,
    default: Date.now,
  },
});

module.exports = mongoose.model('Url', UrlSchema);

父对象的键是将被存储在数据库里面的键。我们定义每个数据键。注意,有些键有一个必填字段,其他键有一个默认值。

最后,我们使用module.exports = mongoose.model('Url', UrlSchema); 输出模式。mongoose.model 里面的第一个参数是要存储的数据的单数形式,第二个参数是模式本身。

建立URL和索引路由

URL路由将从原始URL创建一个短的URL,并将其存储在数据库中。在根目录下创建一个名为routes 的文件夹,并在其中创建一个名为urls.js 的文件。我们将在这里使用Express路由器。首先,导入所有必要的包,像这样。

const Express = require('express');
const router = Express.Router();
const shortid = require('shortid');
const Url = require('../models/Url');
const utils = require('../utils/utils');
require('dotenv').config({ path: '../config/.env' });

utils 文件夹内的utils.js 文件由一个函数组成,用于检查传递的 URL 是否有效。下面是utils.js 文件的代码。

function validateUrl(value) {
  return /^(?:(?:(?:https?|ftp):)?\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))(?::\\d{2,5})?(?:[/?#]\\S*)?$/i.test(
    value
  );
}

module.exports = { validateUrl };

我们将使用urls.js 文件中的HTTP发布请求来生成并发布细节到数据库。

const Express = require('express');
const router = Express.Router();
const shortid = require('shortid');
const Url = require('../models/Url');
const utils = require('../utils/utils');
require('dotenv').config({ path: '../config/.env' });

// Short URL Generator
router.post('/short', async (req, res) => {
  const { origUrl } = req.body;
  const base = process.env.BASE;

  const urlId = shortid.generate();
  if (utils.validateUrl(origUrl)) {
    try {
      let url = await Url.findOne({ origUrl });
      if (url) {
        res.json(url);
      } else {
        const shortUrl = `${base}/${urlId}`;

        url = new Url({
          origUrl,
          shortUrl,
          urlId,
          date: new Date(),
        });

        await url.save();
        res.json(url);
      }
    } catch (err) {
      console.log(err);
      res.status(500).json('Server Error');
    }
  } else {
    res.status(400).json('Invalid Original Url');
  }
});

module.exports = router;

const { origUrl } = req.body; 将从HTTP请求体中提取origUrl 的值。然后我们将基本的URL存储到一个变量中。const urlId = shortid.generate(); 是生成并存储一个短的ID到一个变量中。

一旦它被生成,我们使用我们的函数从utils 目录中检查原始URL是否有效。对于有效的URL,我们进入try 块。

在这里,我们首先用Url.findOne({ origUrl }); mongoose方法搜索原始URL是否已经存在于我们的数据库中。如果找到了,我们以JSON格式返回数据;否则,我们创建一个结合基本URL和短ID的短URL。

然后,使用我们的mongoose模型,我们将字段传递给模型构造函数,并用url.save(); 方法将其保存到数据库中。保存后,我们以JSON格式返回响应。

try 区块的意外错误在catch 区块中处理,无效的URL在我们的validateUrl 函数中返回false ,就会发回URL无效的信息。最后,我们导出路由器。

以前,我们需要安装body-parser 包,但现在它已经集成到Express中了,所以回到app.js 文件,添加这两行来使用body-parser

// Body Parser
app.use(Express.urlencoded({ extended: true }));
app.use(Express.json());

这两行帮助我们读取传入的请求。在这两行代码之后,导入URL路由。

app.use('/api', require('./routes/urls'));

因为我们使用的是/api 端点,所以我们的完整端点变成了 [http://localhost:3333/api/short](http://localhost:3333/api/short).下面是一个例子。

Dashboard Of URL Shortener With API As An Endpoint In The Example URL

现在在routes 文件夹中创建另一个名为index.js 的文件,以处理重定向过程。在这个文件中,导入必要的依赖项。

在这里,我们首先要在我们的数据库中搜索传来的短的URL ID。如果找到了这个URL,我们将重定向到原来的URL。

const Express = require('express');
const router = Express.Router();
const Url = require('../models/Url');

router.get('/:urlId', async (req, res) =&gt; {
  try {
    const url = await Url.findOne({ urlId: req.params.urlId });
    if (url) {
      url.clicks++;
      url.save();
      return res.redirect(url.origUrl);
    } else res.status(404).json('Not found');
  } catch (err) {
    console.log(err);
    res.status(500).json('Server Error');
  }
});

module.exports = router;

HTTPGET 请求是在:urlId 的帮助下获得URL ID。然后,在try 块内,我们使用Url.findOne 方法找到URL,与我们在urls.js 路由中的做法类似。

如果找到了URL,我们就增加对该URL的点击次数并保存点击量。最后,我们使用return res.redirect(url.origUrl); ,将用户重定向到原始URL。

如果没有找到URL,我们会发送一个JSON消息说没有找到URL。任何未捕获的异常都在catch 块中处理。我们在控制台记录错误,并发送一个 "服务器错误 "的JSON消息。最后,我们导出路由器。

将路由导入到app.js 文件中,我们的URL缩短器就可以使用了。导入后,我们最终的app.js 文件将看起来像这样。

const Express = require('Express');
const app = Express();
const connectDB = require('./config/db');
require('dotenv').config({ path: './config/.env' });

connectDB();

// Body Parser
app.use(Express.urlencoded({ extended: true }));
app.use(Express.json());

app.use('/', require('./routes/index'));
app.use('/api', require('./routes/urls'));

// Server Setup
const PORT = 3333;
app.listen(PORT, () => {
  console.log(`Server is running at PORT ${PORT}`);
});

结论

在这篇文章中,我们学习了如何从头开始建立一个URL缩短服务API。你可以将它与任何你想要的前端集成,甚至建立一个全栈的URL缩短服务。我希望你喜欢阅读这篇文章,并在阅读过程中学习到一些新的东西。你可以在我的GitHub repo上找到完整的源代码。

The postBuilding a URL shortener with Node.jsappeared first onLogRocket Blog.