发布于 2026-01-06 1 阅读
0

使用 Node.js 构建 URL 缩短器 DEV 的全球展示与分享挑战赛,由 Mux 呈现:展示你的项目!

使用 Node.js 构建 URL 缩短器

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

作者:Subha Chanda ✏️

像BitlyCuttly这样的网址缩短服务非常流行。在本文中,我们将创建一个类似的工具,通过构建一个 API 服务来缩短用户提供的网址。

演示获取请求 URL 缩短 API 服务

本项目将使用MongoDBNode.js,因此您应该具备它们的基本知识才能学习本教程。

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

我们先来规划一下构建流程,这非常简单。对于传递给我们 API 的每个 URL,我们会生成一个唯一的 ID,并以此创建一个短 URL。然后,长 URL、短 URL 和唯一 ID 将被存储在数据库中。

当用户GET向短链接发送请求时,系统会在数据库中查找该链接,并将用户重定向到对应的原始链接。听起来很复杂?别担心,我们会讲解您需要了解的一切。

初始化应用程序并安装 MongoDB 依赖项

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

让我们使用项目目录中的命令npm init进行初始化。项目初始化完成后,我们将安装所需的依赖项。我们需要的依赖项有:

  • .envdotenv:此软件包从名为to 的文件中加载环境变量。process.env
  • Express.js:这是一个基于 Node.js 的简洁而灵活的 Web 应用程序框架。
  • Mongoose:这是一个用于 Node.js 的 MongoDB 对象建模工具。
  • ShortId:此软件包允许我们为 URL 生成短 ID。

我们唯一需要的开发者依赖项是 nodemon。nodemon 是一个简单的工具,它会在文件发生更改时自动重启 Node.js 服务器。

现在,我们来安装依赖项。要安装应用程序所需的依赖项,我们将使用以下命令:

npm i dotenv express mongoose shortid
Enter fullscreen mode Exit fullscreen mode

安装完所有依赖项后,我们将安装开发者依赖项:

npm i -D nodemon
Enter fullscreen mode Exit fullscreen mode

让我们使用 Express 在文件中创建服务器app.js。要设置 Express 服务器,我们需要将 Express 包导入到文件中app.js。导入包后,对其进行初始化并将其存储在一个名为 `<variable_name>` 的变量中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}`);
});
Enter fullscreen mode Exit fullscreen mode

我使用端口3333来运行服务器。Expresslisten中的该方法会启动一个 UNIX 套接字,并监听指定端口上的连接。

现在,.env在该文件夹内创建一个文件config,用于存储 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
Enter fullscreen mode Exit fullscreen mode

请记住将<password>MongoDB URI 中的字段替换为您的数据库密码。

将数据库连接到应用程序

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

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

由于该文件不在根目录下,因此需要将对象键传递到 dotenv 配置中。我们通过这种方式传递文件path的位置.env.env

connectDB现在,在名为 `<filename>` 的文件夹中db.js创建一个名为 `<unsynchronized_function>` 的异步函数config。本文将使用 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;
Enter fullscreen mode Exit fullscreen mode

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

  • useNewUrlParser: true底层 MongoDB 驱动程序已弃用当前的连接字符串解析器。因此,它添加了一个新标志。如果连接在使用新的字符串解析器时遇到任何问题,它可以回退到旧的解析器。
  • useUnifiedTopology: true默认情况下,此项已启用false。此处将其设置为启用true,以便可以使用 MongoDB 驱动程序的新连接管理引擎。

如果语句中出现任何错误catch,我们将向控制台输出错误信息并退出process.exit(1)。最后,我们使用 `.` 导出函数module.exports

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

在 MongoDB 中创建 Mongoose schema

我们将使用 Mongoose schema 来确定数据在 MongoDB 中的存储方式。本质上,Mongoose schema 就是一个数据模型。让我们Url.js在一个models文件夹内创建一个名为 `.mongoose.schema` 的文件。在这里导入 Mongoose,然后使用mongoose.Schema构造函数创建 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);
Enter fullscreen mode Exit fullscreen mode

父对象键是将要存储在数据库中的键。我们定义每个数据键。请注意,某些键是必填字段,而其他键则有默认值。

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

构建 URL 和索引路由

URL路由会根据原始URL生成一个短URL并将其存储在数据库中。routes在根目录下创建一个名为`<folder_name>`的文件夹,并urls.js在其中创建一个名为`<file_name>`的文件。这里我们将使用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' });
Enter fullscreen mode Exit fullscreen mode

utils.js文件夹内的文件包含utils一个函数,用于检查传入的 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 };
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

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

生成 URL 后,我们使用utils目录中的函数检查原始 URL 是否有效。如果 URL 有效,则进入相应的try代码块。

首先,我们使用Url.findOne({ origUrl });Mongoose 方法在数据库中查找原始 URL 是否已存在。如果存在,则以 JSON 格式返回数据;否则,我们将原始 URL 和短 ID 组合成一个短 URL。

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

try该代码块会处理意外错误,并且如果函数catch返回无效 URL,则会返回一条消息,提示该 URL 无效。最后,我们导出路由。falsevalidateUrl

之前我们需要安装 body-parser 包,但现在它已经集成到 Express 中了。所以回到app.js文件并添加以下两行代码即可使用 body-parser:

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

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

app.use('/api', require('./routes/urls'));
Enter fullscreen mode Exit fullscreen mode

因为我们使用了/api端点,所以完整的端点变为http://localhost:3333/api/short。以下是一个示例。

示例 URL 中以 API 为端点的 URL 缩短服务控制面板

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

首先,我们会搜索数据库中传入的短链接ID。如果找到该链接,我们会将其重定向到原始链接。

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;
Enter fullscreen mode Exit fullscreen mode

HTTPGET请求借助 . 获取 URL ID :urlId。然后,在try代码块中,我们使用 . 方法查找 URL Url.findOne,类似于我们在urls.js路由中所做的。

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

如果找不到 URL,我们会发送一条 JSON 消息,提示 URL 未找到。任何未捕获的异常都会在该catch代码块中处理。我们会将错误记录到控制台,并发送一条“服务器错误”的 JSON 消息。最后,我们导出路由。

将路由导入app.js文件后,我们的网址缩短器即可使用。导入后,最终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}`);
});
Enter fullscreen mode Exit fullscreen mode

结论

在本文中,我们学习了如何从零开始构建一个网址缩短服务 API。您可以将其与任何前端集成,甚至可以构建一个全栈网址缩短服务。希望您喜欢阅读本文,并从中有所收获。您可以在我的GitHub 代码库中找到完整的源代码。


仅需 200 毫秒 ✔️ 监控生产环境中失败和缓慢的网络请求

部署基于 Node 的 Web 应用或网站并不难,难的是如何确保 Node 实例持续为应用提供资源。如果您希望确保对后端或第三方服务的请求都能成功,不妨试试 LogRocket

LogRocket 网络请求监控

LogRocket就像 Web 应用的 DVR,它会记录网站上发生的一切。您无需猜测问题原因,即可汇总并报告有问题的网络请求,从而快速了解根本原因。

LogRocket 可以检测您的应用,记录页面加载时间、首字节到达时间、慢网络请求等基准性能指标,并记录 Redux、NgRx 和 Vuex 的操作/状态。立即开始免费监控

文章来源:https://dev.to/logrocket/building-a-url-shortener-with-node-js-52oe